[Perf] Chat List: Some optimizations

This commit is contained in:
Alexander Zinchuk 2021-11-27 17:40:52 +01:00
parent f09de4ca28
commit 1a6f0bb28f
4 changed files with 78 additions and 60 deletions

View File

@ -14,7 +14,9 @@ import { ALL_CHATS_PRELOAD_DISABLED, CHAT_HEIGHT_PX, CHAT_LIST_SLICE } from '../
import { IS_ANDROID, IS_MAC_OS, IS_PWA } from '../../../util/environment';
import usePrevious from '../../../hooks/usePrevious';
import { mapValues, pick } from '../../../util/iteratees';
import { getChatOrder, prepareChatList, prepareFolderListIds } from '../../../modules/helpers';
import {
getChatOrder, prepareChatList, prepareFolderListIds, reduceChatList,
} from '../../../modules/helpers';
import {
selectChatFolder, selectNotifyExceptions, selectNotifySettings,
} from '../../../modules/selectors';
@ -80,19 +82,20 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
: [listIds, orderedPinnedIds];
}, [folderType, chatFolder, chatsById, usersById, notifySettings, notifyExceptions, listIds, orderedPinnedIds]);
const [orderById, orderedIds] = useMemo(() => {
const [orderById, orderedIds, chatArrays] = useMemo(() => {
if (!currentListIds || (folderType === 'folder' && !chatFolder)) {
return [];
}
const newChatArrays = prepareChatList(chatsById, currentListIds, currentPinnedIds, folderType);
const singleList = [...newChatArrays.pinnedChats, ...newChatArrays.otherChats];
const singleList = ([] as ApiChat[]).concat(newChatArrays.pinnedChats, newChatArrays.otherChats);
const newOrderedIds = singleList.map(({ id }) => id);
const newOrderById = singleList.reduce((acc, chat, i) => {
acc[chat.id] = i;
return acc;
}, {} as Record<string, number>);
return [newOrderById, newOrderedIds];
return [newOrderById, newOrderedIds, newChatArrays];
}, [currentListIds, currentPinnedIds, folderType, chatFolder, chatsById]);
const prevOrderById = usePrevious(orderById);
@ -119,8 +122,13 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
folderType === 'all' && !ALL_CHATS_PRELOAD_DISABLED,
);
// TODO Refactor to not call `prepareChatList` twice
const chatArrays = viewportIds && prepareChatList(chatsById, viewportIds, currentPinnedIds, folderType);
const viewportChatArrays = useMemo(() => {
if (!viewportIds || !chatArrays) {
return undefined;
}
return reduceChatList(chatArrays, viewportIds);
}, [chatArrays, viewportIds]);
useEffect(() => {
if (lastSyncTime && folderType === 'all') {
@ -133,7 +141,7 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
function renderChats() {
const viewportOffset = orderedIds!.indexOf(viewportIds![0]);
const pinnedOffset = viewportOffset + chatArrays!.pinnedChats.length;
const pinnedOffset = viewportOffset + viewportChatArrays!.pinnedChats.length;
return (
<div
@ -142,7 +150,7 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
style={IS_ANDROID ? `height: ${orderedIds!.length * CHAT_HEIGHT_PX}px` : undefined}
teactFastList
>
{chatArrays!.pinnedChats.map(({ id }, i) => (
{viewportChatArrays!.pinnedChats.map(({ id }, i) => (
<Chat
key={id}
teactOrderKey={i}
@ -155,7 +163,7 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
style={`top: ${(viewportOffset + i) * CHAT_HEIGHT_PX}px;`}
/>
))}
{chatArrays!.otherChats.map((chat, i) => (
{viewportChatArrays!.otherChats.map((chat, i) => (
<Chat
key={chat.id}
teactOrderKey={getChatOrder(chat)}
@ -210,7 +218,7 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
noFastList
noScrollRestore
>
{viewportIds?.length && chatArrays ? (
{viewportIds?.length && viewportChatArrays ? (
renderChats()
) : viewportIds && !viewportIds.length ? (
(

View File

@ -24,8 +24,8 @@ export function useChatAnimationType(orderDiffById: Record<string, number>) {
if (
orderDiff === Infinity
|| orderDiff === -Infinity
|| (movesUp(chatId) && numberOfUp <= numberOfDown)
|| (movesDown(chatId) && numberOfDown < numberOfUp)
|| (numberOfUp <= numberOfDown && movesUp(chatId))
|| (numberOfDown < numberOfUp && movesDown(chatId))
) {
return ChatAnimationTypes.Opacity;
}

View File

@ -23,7 +23,7 @@ type DispatchProps = Pick<GlobalActions, 'cancelMessageMediaDownload'>;
const startedDownloads = new Set<string>();
const DownloadsManager: FC<StateProps & DispatchProps> = ({
const DownloadManager: FC<StateProps & DispatchProps> = ({
activeDownloads,
messages,
cancelMessageMediaDownload,
@ -78,4 +78,4 @@ export default memo(withGlobal(
};
},
(setGlobal, actions): DispatchProps => pick(actions, ['cancelMessageMediaDownload']),
)(DownloadsManager));
)(DownloadManager));

View File

@ -223,11 +223,7 @@ export function getChatSlowModeOptions(chat?: ApiChat) {
}
export function getChatOrder(chat: ApiChat) {
return Math.max(
chat.joinDate || 0,
chat.draftDate || 0,
chat.lastMessage ? chat.lastMessage.date : 0,
);
return Math.max(chat.joinDate || 0, chat.draftDate || 0, chat.lastMessage?.date || 0);
}
export function isChatArchived(chat: ApiChat) {
@ -370,52 +366,66 @@ export function prepareChatList(
orderedPinnedIds?: string[],
folderType: 'all' | 'archived' | 'folder' = 'all',
) {
function chatFilter(chat?: ApiChat) {
if (!chat || !chat.lastMessage || chat.migratedTo) {
return false;
}
switch (folderType) {
case 'all':
if (isChatArchived(chat)) {
return false;
}
break;
case 'archived':
if (!isChatArchived(chat)) {
return false;
}
break;
}
return !chat.isRestricted && !chat.isNotJoined;
}
const listedChats = listIds
.map((id) => chatsById[id])
.filter(chatFilter);
const listIdsSet = new Set(listIds);
const pinnedChats = orderedPinnedIds
? (
orderedPinnedIds
.map((id) => chatsById[id])
.filter(chatFilter)
.filter((chat) => listIdsSet.has(chat.id))
)
: [];
const orderedPinnedIdsSet = orderedPinnedIds ? new Set(orderedPinnedIds) : undefined;
const otherChats = orderBy(
orderedPinnedIds
? listedChats.filter((chat) => !orderedPinnedIds.includes(chat.id))
: listedChats,
getChatOrder,
'desc',
);
const pinnedChats = orderedPinnedIds?.reduce((acc, id) => {
const chat = chatsById[id];
if (chat && listIdsSet.has(chat.id) && chatFilter(chat, folderType)) {
acc.push(chat);
}
return acc;
}, [] as ApiChat[]) || [];
const otherChats = listIds.reduce((acc, id) => {
const chat = chatsById[id];
if (chat && (!orderedPinnedIdsSet || !orderedPinnedIdsSet.has(chat.id)) && chatFilter(chat, folderType)) {
acc.push(chat);
}
return acc;
}, [] as ApiChat[]);
const otherChatsOrdered = orderBy(otherChats, getChatOrder, 'desc');
return {
pinnedChats,
otherChats,
otherChats: otherChatsOrdered,
};
}
function chatFilter(chat: ApiChat, folderType: 'all' | 'archived' | 'folder') {
if (!chat.lastMessage || chat.migratedTo) {
return false;
}
switch (folderType) {
case 'all':
if (isChatArchived(chat)) {
return false;
}
break;
case 'archived':
if (!isChatArchived(chat)) {
return false;
}
break;
}
return !chat.isRestricted && !chat.isNotJoined;
}
export function reduceChatList(
chatArrays: { pinnedChats: ApiChat[]; otherChats: ApiChat[] },
filteredIds: string[],
) {
const filteredIdsSet = new Set(filteredIds);
return {
pinnedChats: chatArrays.pinnedChats.filter(({ id }) => filteredIdsSet.has(id)),
otherChats: chatArrays.otherChats.filter(({ id }) => filteredIdsSet.has(id)),
};
}