perf: optimize the performance of the member list in the case of large data

and optimize the content of the network and view patterns
pull/109/head
moonrailgun 2 years ago
parent a2553c11d8
commit 4c77d144e8

@ -1,4 +1,4 @@
import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useQueries, useQuery, useQueryClient } from '@tanstack/react-query';
import { import {
fetchUserInfo, fetchUserInfo,
getUserOnlineStatus, getUserOnlineStatus,
@ -40,11 +40,13 @@ export function useCachedOnlineStatus(
ids: string[], ids: string[],
onOnlineStatusUpdate?: (onlineStatus: boolean[]) => void onOnlineStatusUpdate?: (onlineStatus: boolean[]) => void
): boolean[] { ): boolean[] {
const staleTime = 20 * 1000; // 缓存20s
const { data, isSuccess } = useQuery( const { data, isSuccess } = useQuery(
['onlineStatus', ids.join(',')], ['onlineStatus', ids.join(',')],
() => getUserOnlineStatus(ids), () => getUserOnlineStatus(ids),
{ {
staleTime: 10 * 1000, // 缓存10s staleTime,
} }
); );

@ -2,7 +2,10 @@ import { request } from '../api/request';
import { buildCachedRequest } from '../cache/utils'; import { buildCachedRequest } from '../cache/utils';
import { sharedEvent } from '../event'; import { sharedEvent } from '../event';
import { SYSTEM_USERID } from '../utils/consts'; import { SYSTEM_USERID } from '../utils/consts';
import { createAutoMergedRequest } from '../utils/request'; import {
createAutoMergedRequest,
createAutoSplitRequest,
} from '../utils/request';
import _pick from 'lodash/pick'; import _pick from 'lodash/pick';
import _uniq from 'lodash/uniq'; import _uniq from 'lodash/uniq';
import _flatten from 'lodash/flatten'; import _flatten from 'lodash/flatten';
@ -250,14 +253,18 @@ export async function searchUserWithUniqueName(
} }
const _fetchUserInfo = createAutoMergedRequest<string, UserBaseInfo>( const _fetchUserInfo = createAutoMergedRequest<string, UserBaseInfo>(
async (userIds) => { createAutoSplitRequest(
// 这里用post是为了防止一次性获取的userId过多超过url限制 async (userIds) => {
const { data } = await request.post('/api/user/getUserInfoList', { // 这里用post是为了防止一次性获取的userId过多超过url限制
userIds, const { data } = await request.post('/api/user/getUserInfoList', {
}); userIds,
});
return data;
} return data;
},
'serial',
1000
)
); );
/** /**
* *
@ -277,21 +284,26 @@ export async function fetchUserInfo(userId: string): Promise<UserBaseInfo> {
} }
const _fetchUserOnlineStatus = createAutoMergedRequest<string[], boolean[]>( const _fetchUserOnlineStatus = createAutoMergedRequest<string[], boolean[]>(
async (userIdsList) => { createAutoSplitRequest(
const uniqList = _uniq(_flatten(userIdsList)); async (userIdsList) => {
// 这里用post是为了防止一次性获取的userId过多超过url限制 const uniqList = _uniq(_flatten(userIdsList));
const { data } = await request.post('/api/gateway/checkUserOnline', { // 这里用post是为了防止一次性获取的userId过多超过url限制
userIds: uniqList, const { data } = await request.post('/api/gateway/checkUserOnline', {
}); userIds: uniqList,
});
const map = _zipObject<boolean>(uniqList, data);
const map = _zipObject<boolean>(uniqList, data);
// 将请求结果根据传输来源重新分组
return userIdsList.map((userIds) => // 将请求结果根据传输来源重新分组
userIds.map((userId) => map[userId] ?? false) return userIdsList.map((userIds) =>
); userIds.map((userId) => map[userId] ?? false)
} );
},
'serial',
1000
)
); );
/** /**
* 线 * 线
*/ */

@ -1,3 +1,6 @@
import _chunk from 'lodash/chunk';
import _flatten from 'lodash/flatten';
interface QueueItem<T, R> { interface QueueItem<T, R> {
params: T; params: T;
resolve: (r: R) => void; resolve: (r: R) => void;
@ -52,3 +55,36 @@ export function createAutoMergedRequest<T, R>(
}); });
}; };
} }
/**
*
*/
export function createAutoSplitRequest<Key, Item>(
fn: (keys: Key[]) => Promise<Item[]>,
type: 'serial' | 'parallel',
limit = 100
): (arr: Key[]) => Promise<Item[]> {
return async (arr: Key[]): Promise<Item[]> => {
const groups = _chunk(arr, limit);
if (type === 'serial') {
const list: Item[] = [];
for (const group of groups) {
const res = await fn(group);
if (Array.isArray(res)) {
list.push(...res);
} else {
console.warn('[createAutoSplitRequest] fn should be return array');
}
}
return list;
} else if (type === 'parallel') {
const res = await Promise.all(groups.map((group) => fn(group)));
return _flatten(res);
}
return [];
};
}

@ -58,7 +58,6 @@ const GroupCustomWebPanelRender: React.FC<{ html: string }> = (props) => {
const doc = ref.current.contentWindow.document; const doc = ref.current.contentWindow.document;
doc.open(); doc.open();
console.log('html', xss.process(html));
doc.writeln(getInjectedStyle(), xss.process(html)); doc.writeln(getInjectedStyle(), xss.process(html));
doc.close(); doc.close();
}, [html]); }, [html]);

@ -15,6 +15,7 @@ import {
import { Problem } from '@/components/Problem'; import { Problem } from '@/components/Problem';
import { useGroupMemberAction } from '@/hooks/useGroupMemberAction'; import { useGroupMemberAction } from '@/hooks/useGroupMemberAction';
import { UserPopover } from '@/components/popover/UserPopover'; import { UserPopover } from '@/components/popover/UserPopover';
import { Virtuoso } from 'react-virtuoso';
interface MembersPanelProps { interface MembersPanelProps {
groupId: string; groupId: string;
@ -44,7 +45,7 @@ export const MembersPanel: React.FC<MembersPanelProps> = React.memo((props) => {
generateActionMenu, generateActionMenu,
} = useGroupMemberAction(groupId); } = useGroupMemberAction(groupId);
const groupedMembers = useMemo(() => { const sortedMembers = useMemo(() => {
const online: UserBaseInfo[] = []; const online: UserBaseInfo[] = [];
const offline: UserBaseInfo[] = []; const offline: UserBaseInfo[] = [];
@ -56,10 +57,7 @@ export const MembersPanel: React.FC<MembersPanelProps> = React.memo((props) => {
} }
}); });
return { return [...online, ...offline];
online,
offline,
};
}, [userInfos, membersOnlineStatus]); }, [userInfos, membersOnlineStatus]);
if (!groupInfo) { if (!groupInfo) {
@ -98,7 +96,7 @@ export const MembersPanel: React.FC<MembersPanelProps> = React.memo((props) => {
}; };
return ( return (
<div> <div className="h-full flex flex-col">
<div className="p-2"> <div className="p-2">
<Input <Input
placeholder={t('搜索成员')} placeholder={t('搜索成员')}
@ -109,21 +107,13 @@ export const MembersPanel: React.FC<MembersPanelProps> = React.memo((props) => {
/> />
</div> </div>
{isSearching ? ( <div className="flex-1">
filteredGroupMembers.map(renderUser) <Virtuoso
) : ( className="h-full"
<> data={isSearching ? filteredGroupMembers : sortedMembers}
{groupedMembers.online.map(renderUser)} itemContent={(i, item) => renderUser(item)}
/>
{groupedMembers.offline.length > 0 && ( </div>
<>
<Divider>{t('以下用户已离线')}</Divider>
{groupedMembers.offline.map(renderUser)}
</>
)}
</>
)}
</div> </div>
); );
}); });

@ -6,6 +6,7 @@ import { Dropdown, Input, MenuProps } from 'antd';
import React from 'react'; import React from 'react';
import { Icon } from 'tailchat-design'; import { Icon } from 'tailchat-design';
import { t, UserBaseInfo } from 'tailchat-shared'; import { t, UserBaseInfo } from 'tailchat-shared';
import { Virtuoso } from 'react-virtuoso';
/** /**
* *
@ -57,9 +58,11 @@ export const GroupMember: React.FC<{ groupId: string }> = React.memo(
</div> </div>
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
{isSearching <Virtuoso
? filteredGroupMembers.map(renderUser) style={{ height: '100%' }}
: userInfos.map(renderUser)} data={isSearching ? filteredGroupMembers : userInfos}
itemContent={(index, item) => renderUser(item)}
/>
</div> </div>
</div> </div>
); );

Loading…
Cancel
Save