Skip to content

Commit

Permalink
perf(ui): optimizing virtual-scroll
Browse files Browse the repository at this point in the history
  • Loading branch information
xiejay97 committed Aug 4, 2022
1 parent fb8a7b3 commit d0205a3
Show file tree
Hide file tree
Showing 16 changed files with 363 additions and 346 deletions.
44 changes: 22 additions & 22 deletions packages/ui/src/components/auto-complete/AutoComplete.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { DNestedChildren } from '../../utils/global';
import type { DFocusVisibleRenderProps } from '../_focus-visible';
import type { DVirtualScrollRef } from '../virtual-scroll';
import type { DVirtualScrollPerformance, DVirtualScrollRef } from '../virtual-scroll';

import { isUndefined } from 'lodash';
import React, { useState, useId, useCallback, useRef, useImperativeHandle, useEffect } from 'react';
import React, { useState, useId, useCallback, useRef, useImperativeHandle, useEffect, useMemo } from 'react';
import ReactDOM from 'react-dom';

import {
Expand Down Expand Up @@ -266,6 +266,21 @@ function AutoComplete<T extends DAutoCompleteItem>(
},
});

const vsPerformance = useMemo<DVirtualScrollPerformance<DNestedChildren<T>>>(
() => ({
dList,
dItemSize: 32,
dItemNested: (item) => ({
list: item.children,
emptySize: 32,
asItem: false,
}),
dItemKey: (item) => item.value,
dFocusable: canSelectItem,
}),
[canSelectItem, dList]
);

return (
<>
<DFocusVisible onFocusVisibleChange={setFocusVisible}>
Expand Down Expand Up @@ -353,12 +368,12 @@ function AutoComplete<T extends DAutoCompleteItem>(
</div>
)}
<DVirtualScroll
{...vsPerformance}
ref={dVSRef}
id={listId}
className={`${dPrefix}auto-complete__list`}
role="listbox"
aria-activedescendant={isUndefined(focusItem) ? undefined : getItemId(focusItem.value)}
dList={dList}
dItemRender={(item, index, { iARIA, iChildren }, parent) => {
const { value: itemValue, disabled: itemDisabled, children } = item;

Expand All @@ -380,13 +395,7 @@ function AutoComplete<T extends DAutoCompleteItem>(
>
<div className={`${dPrefix}auto-complete__option-content`}>{itemNode}</div>
</li>
{children.length === 0 ? (
<li className={`${dPrefix}auto-complete__empty`} style={{ paddingLeft: 12 + 8 }}>
<div className={`${dPrefix}auto-complete__option-content`}>{t('No Data')}</div>
</li>
) : (
iChildren
)}
{iChildren}
</ul>
);
}
Expand Down Expand Up @@ -416,23 +425,14 @@ function AutoComplete<T extends DAutoCompleteItem>(
</li>
);
}}
dItemSize={(item) => {
if (item.children && item.children.length === 0) {
return 64;
}
return 32;
}}
dItemNested={(item) => item.children}
dItemKey={(item) => item.value}
dFocusable={canSelectItem}
dFocusItem={focusItem}
dSize={264}
dPadding={4}
dEmpty={
<li className={`${dPrefix}auto-complete__empty`}>
dEmptyRender={(item) => (
<li className={`${dPrefix}auto-complete__empty`} style={{ paddingLeft: item ? 12 + 8 : undefined }}>
<div className={`${dPrefix}auto-complete__option-content`}>{t('No Data')}</div>
</li>
}
)}
onScrollEnd={onScrollBottom}
/>
</div>
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/components/cascader/Cascader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ function Cascader<V extends DId, T extends DCascaderItem<V>>(
[dCustomSearch, searchValue]
);
const sortFn = dCustomSearch?.sort;
const searchList = (() => {
const searchList = useMemo(() => {
if (!hasSearch) {
return [];
}
Expand Down Expand Up @@ -212,7 +212,7 @@ function Cascader<V extends DId, T extends DCascaderItem<V>>(
searchList.sort((a, b) => sortFn(a[TREE_NODE_KEY].origin, b[TREE_NODE_KEY].origin));
}
return searchList;
})();
}, [dMultiple, dOnlyLeafSelectable, filterFn, hasSearch, renderNodes, sortFn]);

const [_noSearchFocusNode, setNoSearchFocusNode] = useState<AbstractTreeNode<V, T> | undefined>();
const noSearchFocusNode = (() => {
Expand Down
23 changes: 15 additions & 8 deletions packages/ui/src/components/cascader/List.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { DId } from '../../utils/global';
import type { AbstractTreeNode, MultipleTreeNode } from '../tree/node';
import type { DVirtualScrollRef } from '../virtual-scroll';
import type { DVirtualScrollPerformance, DVirtualScrollRef } from '../virtual-scroll';
import type { DCascaderItem } from './Cascader';
import type { Subject } from 'rxjs';

import { isUndefined } from 'lodash';
import { useEffect, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';

import { usePrefixConfig, useTranslation, useEventCallback } from '../../hooks';
import { LoadingOutlined, RightOutlined } from '../../icons';
Expand Down Expand Up @@ -163,16 +163,26 @@ export function DList<ID extends DId, T extends DCascaderItem<ID>>(props: DListP
}
}, [handleKeyDown, isFocus, onKeyDown$, shouldInitFocus]);

const vsPerformance = useMemo<DVirtualScrollPerformance<AbstractTreeNode<ID, T>>>(
() => ({
dList: dNodes,
dItemSize: 32,
dItemKey: (item) => item.id,
dFocusable: (item) => item.enabled,
}),
[dNodes]
);

return (
<>
<DVirtualScroll
{...vsPerformance}
ref={dVSRef}
id={dListId}
className={`${dPrefix}cascader__list`}
role="listbox"
aria-multiselectable={dMultiple}
aria-activedescendant={dRoot && !isUndefined(dFocusNode) ? dGetItemId(dFocusNode.id) : undefined}
dList={dNodes}
dItemRender={(item, index, { iARIA }) => (
<li
{...iARIA}
Expand Down Expand Up @@ -213,17 +223,14 @@ export function DList<ID extends DId, T extends DCascaderItem<ID>>(props: DListP
)}
</li>
)}
dItemSize={32}
dItemKey={(item) => item.id}
dFocusable={(item) => item.enabled}
dFocusItem={inFocusNode}
dSize={264}
dPadding={4}
dEmpty={
dEmptyRender={() => (
<li className={`${dPrefix}cascader__empty`}>
<div className={`${dPrefix}cascader__option-content`}>{t('No Data')}</div>
</li>
}
)}
/>
{inFocusNode && !inFocusNode.origin.loading && inFocusNode.children && (
<DList {...props} dListId={undefined} dNodes={inFocusNode.children} dRoot={false}></DList>
Expand Down
23 changes: 15 additions & 8 deletions packages/ui/src/components/cascader/SearchList.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { DId } from '../../utils/global';
import type { MultipleTreeNode } from '../tree/node';
import type { DVirtualScrollRef } from '../virtual-scroll';
import type { DVirtualScrollPerformance, DVirtualScrollRef } from '../virtual-scroll';
import type { DCascaderItem, DSearchItem } from './Cascader';
import type { Subject } from 'rxjs';

import { isUndefined } from 'lodash';
import React, { useEffect, useRef } from 'react';
import React, { useEffect, useMemo, useRef } from 'react';

import { useEventCallback, usePrefixConfig, useTranslation } from '../../hooks';
import { getClassName } from '../../utils';
Expand Down Expand Up @@ -120,15 +120,25 @@ export function DSearchList<ID extends DId, T extends DCascaderItem<ID>>(props:
};
}, [handleKeyDown, onKeyDown$]);

const vsPerformance = useMemo<DVirtualScrollPerformance<DSearchItem<ID, T>>>(
() => ({
dList: dList,
dItemSize: 32,
dItemKey: (item) => item.value,
dFocusable: (item) => item[TREE_NODE_KEY].enabled,
}),
[dList]
);

return (
<DVirtualScroll
{...vsPerformance}
ref={dVSRef}
id={dListId}
className={`${dPrefix}cascader__list`}
role="listbox"
aria-multiselectable={dMultiple}
aria-activedescendant={isUndefined(dFocusItem) ? undefined : dGetItemId(dFocusItem.value)}
dList={dList}
dItemRender={(item, index, { iARIA }) => {
const node = item[TREE_NODE_KEY];
let inSelected = node.checked;
Expand Down Expand Up @@ -167,17 +177,14 @@ export function DSearchList<ID extends DId, T extends DCascaderItem<ID>>(props:
</li>
);
}}
dItemSize={32}
dItemKey={(item) => item.value}
dFocusable={(item) => item[TREE_NODE_KEY].enabled}
dFocusItem={dFocusItem}
dSize={264}
dPadding={4}
dEmpty={
dEmptyRender={() => (
<li className={`${dPrefix}cascader__empty`}>
<div className={`${dPrefix}cascader__option-content`}>{t('No Data')}</div>
</li>
}
)}
/>
);
}
18 changes: 7 additions & 11 deletions packages/ui/src/components/cascader/demos/2.Searchable.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,17 @@ const list = Array(3)
})),
})),
}));
const search = {
filter: (value, item) => {
return item.label.endsWith(value);
},
sort: () => -1,
};
export default function Demo() {
return (
<>
<DCascader dList={list} dPlaceholder="Search" dSearchable></DCascader>
<DCascader
dList={list}
dPlaceholder="Custom search"
dSearchable
dCustomSearch={{
filter: (value, item) => {
return item.label.endsWith(value);
},
sort: () => -1,
}}
></DCascader>
<DCascader dList={list} dPlaceholder="Custom search" dSearchable dCustomSearch={search}></DCascader>
</>
);
}
Expand Down
58 changes: 29 additions & 29 deletions packages/ui/src/components/select/Select.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { DNestedChildren, DId, DSize } from '../../utils/global';
import type { DDropdownItem } from '../dropdown';
import type { DFormControl } from '../form';
import type { DVirtualScrollRef } from '../virtual-scroll';
import type { DVirtualScrollPerformance, DVirtualScrollRef } from '../virtual-scroll';

import { isNull, isUndefined } from 'lodash';
import React, { useState, useId, useCallback, useMemo, useRef } from 'react';
Expand Down Expand Up @@ -160,16 +160,16 @@ function Select<V extends DId, T extends DSelectItem<V>>(

const _filterFn = dCustomSearch?.filter;
const filterFn = useCallback(
(searchStr: string, item: T) => {
(item: T) => {
const defaultFilterFn = (item: T) => {
return item.label.includes(searchStr);
return item.label.includes(searchValue);
};
return _filterFn ? _filterFn(searchStr, item) : defaultFilterFn(item);
return _filterFn ? _filterFn(searchValue, item) : defaultFilterFn(item);
},
[_filterFn]
[_filterFn, searchValue]
);
const sortFn = dCustomSearch?.sort;
const searchList = (() => {
const searchList = useMemo(() => {
if (!hasSearch) {
return [];
}
Expand All @@ -189,7 +189,7 @@ function Select<V extends DId, T extends DSelectItem<V>>(
if (createItem && item.value === createItem.value) {
createItem = undefined;
}
if (filterFn(searchValue, item)) {
if (filterFn(item)) {
searchList.push(item);
}
} else {
Expand All @@ -198,7 +198,7 @@ function Select<V extends DId, T extends DSelectItem<V>>(
if (createItem && groupItem.value === createItem.value) {
createItem = undefined;
}
if (filterFn(searchValue, groupItem)) {
if (filterFn(groupItem)) {
groupList.push(groupItem);
}
});
Expand All @@ -215,7 +215,7 @@ function Select<V extends DId, T extends DSelectItem<V>>(
}

return searchList;
})();
}, [dCreateItem, dList, filterFn, hasSearch, searchValue, sortFn]);
const list = hasSearch ? searchList : dList;

const [_noSearchFocusItem, setNoSearchFocusItem] = useState<DNestedChildren<T> | undefined>();
Expand Down Expand Up @@ -339,6 +339,21 @@ function Select<V extends DId, T extends DSelectItem<V>>(
return [selectedNode, suffixNode, selectedLabel];
})();

const vsPerformance = useMemo<DVirtualScrollPerformance<DNestedChildren<T>>>(
() => ({
dList: list,
dItemSize: 32,
dItemNested: (item) => ({
list: item.children,
emptySize: 32,
asItem: false,
}),
dItemKey: (item) => item.value,
dFocusable: canSelectItem,
}),
[canSelectItem, list]
);

return (
<DSelectbox
{...restProps}
Expand Down Expand Up @@ -454,13 +469,13 @@ function Select<V extends DId, T extends DSelectItem<V>>(
</div>
)}
<DVirtualScroll
{...vsPerformance}
ref={dVSRef}
id={listId}
className={`${dPrefix}select__list`}
role="listbox"
aria-multiselectable={dMultiple}
aria-activedescendant={isUndefined(focusItem) ? undefined : getItemId(focusItem.value)}
dList={list}
dItemRender={(item, index, { iARIA, iChildren }, parent) => {
const { label: itemLabel, value: itemValue, disabled: itemDisabled, children } = item;

Expand All @@ -472,13 +487,7 @@ function Select<V extends DId, T extends DSelectItem<V>>(
<li key={itemValue} id={getGroupId(itemValue)} className={`${dPrefix}select__option-group-label`} role="presentation">
<div className={`${dPrefix}select__option-content`}>{node}</div>
</li>
{children.length === 0 ? (
<li className={`${dPrefix}select__empty`} style={{ paddingLeft: 12 + 8 }}>
<div className={`${dPrefix}select__option-content`}>{t('No Data')}</div>
</li>
) : (
iChildren
)}
{iChildren}
</ul>
);
}
Expand Down Expand Up @@ -524,23 +533,14 @@ function Select<V extends DId, T extends DSelectItem<V>>(
</li>
);
}}
dItemSize={(item) => {
if (item.children && item.children.length === 0) {
return 64;
}
return 32;
}}
dItemNested={(item) => item.children}
dItemKey={(item) => item.value}
dFocusable={canSelectItem}
dFocusItem={focusItem}
dSize={264}
dPadding={4}
dEmpty={
<li className={`${dPrefix}select__empty`}>
dEmptyRender={(item) => (
<li className={`${dPrefix}select__empty`} style={{ paddingLeft: item ? 12 + 8 : undefined }}>
<div className={`${dPrefix}select__option-content`}>{t('No Data')}</div>
</li>
}
)}
onScrollEnd={onScrollBottom}
/>
</div>
Expand Down
Loading

0 comments on commit d0205a3

Please sign in to comment.