From 3c4d5e14b591c085af91d5bbe234b88394bd6b2b Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Sat, 20 Nov 2021 20:46:40 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=96=B0=E7=89=88=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E5=88=97=E8=A1=A8=E5=A2=9E=E5=8A=A0=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E6=9B=B4=E5=A4=9A=E4=B8=8E=E8=A7=A6=E5=BA=95=E9=94=81=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChatMessageList/VirtualizedList.new.tsx | 86 ++++++++++++++++--- yarn.lock | 21 +---- 2 files changed, 77 insertions(+), 30 deletions(-) diff --git a/web/src/components/ChatBox/ChatMessageList/VirtualizedList.new.tsx b/web/src/components/ChatBox/ChatMessageList/VirtualizedList.new.tsx index 28619c28..a45421c1 100644 --- a/web/src/components/ChatBox/ChatMessageList/VirtualizedList.new.tsx +++ b/web/src/components/ChatBox/ChatMessageList/VirtualizedList.new.tsx @@ -1,39 +1,103 @@ -import React, { useRef } from 'react'; +import React, { useDebugValue, useMemo, useRef } from 'react'; import { buildMessageItemRow } from './Item'; import type { MessageListProps } from './types'; -import { Virtuoso, VirtuosoGridHandle } from 'react-virtuoso'; +import { + FollowOutputScalarType, + Virtuoso, + VirtuosoGridHandle, +} from 'react-virtuoso'; +import type { ChatMessage } from 'tailchat-shared'; + +const PREPEND_OFFSET = 10 ** 7; + +const virtuosoStyle: React.CSSProperties = { + height: '100%', +}; /** * 新版的虚拟列表 + * 参考: https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageList/VirtualizedMessageList.tsx */ export const VirtualizedMessageList: React.FC = React.memo( (props) => { const listRef = useRef(); const lastMessageId = useRef(''); + const numItemsPrepended = usePrependedMessagesCount(props.messages); + useDebugValue(numItemsPrepended); + const handleLoadMore = () => { - // TODO: 待修复, 这个方法只会被触发一次 lastMessageId.current = props.messages[0]._id; props.onLoadMore().then(() => { listRef.current?.scrollToIndex(50); }); + }; - return false; + const followOutput = (isAtBottom: boolean): FollowOutputScalarType => { + /** + * 如果有新的内容,且当前处于最底部时, 保持在最底部 + */ + return isAtBottom ? 'smooth' : false; + }; + + const itemContent = (virtuosoIndex: number) => { + const index = virtuosoIndex + numItemsPrepended - PREPEND_OFFSET; + + return buildMessageItemRow(props.messages, props.messages[index]._id); }; return ( - buildMessageItemRow(props.messages, data._id) - } + firstItemIndex={PREPEND_OFFSET - numItemsPrepended} + initialTopMostItemIndex={Math.max(props.messages.length - 1, 0)} + totalCount={props.messages.length} + overscan={200} + itemContent={itemContent} alignToBottom={true} startReached={handleLoadMore} + followOutput={followOutput} /> ); } ); VirtualizedMessageList.displayName = 'VirtualizedMessageList'; + +function usePrependedMessagesCount(messages: ChatMessage[]) { + const currentFirstMessageId = messages?.[0]?._id; + const firstMessageId = useRef(currentFirstMessageId); + const earliestMessageId = useRef(currentFirstMessageId); + const previousNumItemsPrepended = useRef(0); + + const numItemsPrepended = useMemo(() => { + if (!messages || !messages.length) { + return 0; + } + // if no new messages were prepended, return early (same amount as before) + if (currentFirstMessageId === earliestMessageId.current) { + return previousNumItemsPrepended.current; + } + + if (!firstMessageId.current) { + firstMessageId.current = currentFirstMessageId; + } + earliestMessageId.current = currentFirstMessageId; + // if new messages were prepended, find out how many + // start with this number because there cannot be fewer prepended items than before + for ( + let i = previousNumItemsPrepended.current; + i < messages.length; + i += 1 + ) { + if (messages[i]._id === firstMessageId.current) { + previousNumItemsPrepended.current = i; + return i; + } + } + return 0; + // TODO: there's a bug here, the messages prop is the same array instance (something mutates it) + // that's why the second dependency is necessary + }, [messages, messages?.length]); + + return numItemsPrepended; +} diff --git a/yarn.lock b/yarn.lock index 839586ec..96f538b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3616,7 +3616,7 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" -clsx@^1.0.4, clsx@^1.1.1: +clsx@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== @@ -4438,7 +4438,7 @@ dom-converter@^0.2.0: dependencies: utila "~0.4" -dom-helpers@^5.0.1, dom-helpers@^5.1.3: +dom-helpers@^5.0.1: version "5.2.1" resolved "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== @@ -9143,11 +9143,6 @@ react-is@^17.0.1: resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-lifecycles-compat@^3.0.4: - version "3.0.4" - resolved "https://registry.npmmirror.com/react-lifecycles-compat/download/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" - integrity sha1-TxonOv38jzSIqMUWv9p4+HI1I2I= - "react-native-storage@npm:@trpgengine/react-native-storage@^1.0.1": version "1.0.1" resolved "https://registry.npmjs.org/@trpgengine/react-native-storage/-/react-native-storage-1.0.1.tgz#7571ec0d837150a4eec123ae7b938084b3c70413" @@ -9231,18 +9226,6 @@ react-virtualized-auto-sizer@^1.0.6: resolved "https://registry.nlark.com/react-virtualized-auto-sizer/download/react-virtualized-auto-sizer-1.0.6.tgz#66c5b1c9278064c5ef1699ed40a29c11518f97ca" integrity sha1-ZsWxySeAZMXvFpntQKKcEVGPl8o= -react-virtualized@^9.22.3: - version "9.22.3" - resolved "https://registry.nlark.com/react-virtualized/download/react-virtualized-9.22.3.tgz#f430f16beb0a42db420dbd4d340403c0de334421" - integrity sha1-9DDxa+sKQttCDb1NNAQDwN4zRCE= - dependencies: - "@babel/runtime" "^7.7.2" - clsx "^1.0.4" - dom-helpers "^5.1.3" - loose-envify "^1.4.0" - prop-types "^15.7.2" - react-lifecycles-compat "^3.0.4" - react-virtuoso@^2.2.7: version "2.2.7" resolved "https://registry.npmmirror.com/react-virtuoso/download/react-virtuoso-2.2.7.tgz?cache=0&sync_timestamp=1636198122025&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Freact-virtuoso%2Fdownload%2Freact-virtuoso-2.2.7.tgz#3aa7243ed79ec6257f0de30679f64e9614533d2e"