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 {
fetchUserInfo,
getUserOnlineStatus,
@ -40,11 +40,13 @@ export function useCachedOnlineStatus(
ids: string[],
onOnlineStatusUpdate?: (onlineStatus: boolean[]) => void
): boolean[] {
const staleTime = 20 * 1000; // 缓存20s
const { data, isSuccess } = useQuery(
['onlineStatus', ids.join(',')],
() => getUserOnlineStatus(ids),
{
staleTime: 10 * 1000, // 缓存10s
staleTime,
}
);

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

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

@ -6,6 +6,7 @@ import { Dropdown, Input, MenuProps } from 'antd';
import React from 'react';
import { Icon } from 'tailchat-design';
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 className="flex-1 overflow-auto">
{isSearching
? filteredGroupMembers.map(renderUser)
: userInfos.map(renderUser)}
<Virtuoso
style={{ height: '100%' }}
data={isSearching ? filteredGroupMembers : userInfos}
itemContent={(index, item) => renderUser(item)}
/>
</div>
</div>
);

Loading…
Cancel
Save