Skip to content

Commit

Permalink
feat(tree): support virtual scroll (#2359)
Browse files Browse the repository at this point in the history
* feat(tree): support virtual scroll

* feat: add treeScroll hooks

* feat(tree): support virtual scroll

* chore: update snapshot

* chore: update demo

* chore: update docs
  • Loading branch information
uyarn authored Jul 12, 2023
1 parent 43d6b94 commit 8d65442
Show file tree
Hide file tree
Showing 17 changed files with 845 additions and 387 deletions.
4 changes: 2 additions & 2 deletions src/hooks/useVirtualScroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ const useVirtualScroll = (container: MutableRefObject<HTMLElement>, params: UseV

// 当前场景是否满足开启虚拟滚动的条件
const isVirtualScroll = useMemo(() => tScroll.type === 'virtual' && tScroll.threshold < data.length, [tScroll, data]);

const getTrScrollTopHeightList = (trHeightList: number[], containerHeight: number) => {
const list: number[] = [];
// 大数据场景不建议使用 forEach 一类函数迭代
Expand Down Expand Up @@ -78,7 +77,7 @@ const useVirtualScroll = (container: MutableRefObject<HTMLElement>, params: UseV
}
};

// 固定高度场景,不需要通过行渲染获取高度(仅非固定高度场景需要
// 仅非固定高度场景需要
const handleRowMounted = (rowData: any) => {
if (!isVirtualScroll || !rowData || tScroll.isFixedRowHeight || !container?.current) return;
const trHeight = rowData.ref.offsetHeight;
Expand Down Expand Up @@ -158,6 +157,7 @@ const useVirtualScroll = (container: MutableRefObject<HTMLElement>, params: UseV
setScrollHeight(data.length * tScroll.rowHeight);
const startIndex = startAndEndIndex[0];
const tmpData = data.slice(startIndex, startIndex + tripleBufferSize);

setVisibleData(tmpData);

const timer = setTimeout(() => {
Expand Down
62 changes: 45 additions & 17 deletions src/tree/Tree.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import React, { forwardRef, useState, useImperativeHandle, useMemo, RefObject, MouseEvent } from 'react';
import React, { forwardRef, useState, useImperativeHandle, useMemo, RefObject, MouseEvent, useRef } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import classNames from 'classnames';
import { TreeNodeState, TreeNodeValue, TypeTreeNodeData, TypeTreeNodeModel } from '../_common/js/tree/types';

import TreeNode from '../_common/js/tree/tree-node';
import { TreeOptionData } from '../common';
import { usePersistFn } from '../_util/usePersistFn';
import { TreeInstanceFunctions, TdTreeProps } from './type';
import { useTreeConfig } from './useTreeConfig';
import useControllable from './useControllable';
import { TreeOptionData, StyledProps } from '../common';
import { TreeItemProps } from './interface';
import TreeItem from './TreeItem';
import { useStore } from './useStore';
import { TreeDraggableContext } from './TreeDraggableContext';

import useControllable from './hooks/useControllable';
import { useStore } from './hooks/useStore';
import { useTreeConfig } from './hooks/useTreeConfig';
import { TreeDraggableContext } from './hooks/TreeDraggableContext';
import parseTNode from '../_util/parseTNode';
import { usePersistFn } from '../_util/usePersistFn';
import useTreeVirtualScroll from './hooks/useTreeVirtualScroll';

export type TreeProps = TdTreeProps;
import type { TreeNodeState, TreeNodeValue, TypeTreeNodeData, TypeTreeNodeModel } from '../_common/js/tree/types';
import type { TreeInstanceFunctions, TdTreeProps } from './type';

export type TreeProps = TdTreeProps & StyledProps;

/**
* 树组件
Expand All @@ -40,6 +44,8 @@ const Tree = forwardRef((props: TreeProps, ref: React.Ref<TreeInstanceFunctions>
transition, // 动画默认开启
expandOnClickNode,
onClick,
scroll,
style,
} = props;

const { value, onChange, expanded, onExpand, onActive, actived } = useControllable(props);
Expand All @@ -65,7 +71,6 @@ const Tree = forwardRef((props: TreeProps, ref: React.Ref<TreeInstanceFunctions>
const newVisibleNodes = nodes?.filter((node) => node.visible);
setVisibleNodes(newVisibleNodes);
}

// 因为是被 useImperativeHandle 依赖的方法,使用 usePersistFn 变成持久化的。或者也可以使用 useCallback
const setExpanded = usePersistFn(
(
Expand All @@ -80,6 +85,13 @@ const Tree = forwardRef((props: TreeProps, ref: React.Ref<TreeInstanceFunctions>
return expanded;
},
);
const treeRef = useRef(null);

const { visibleData, isVirtual, treeNodeStyle, cursorStyle, handleRowMounted } = useTreeVirtualScroll({
treeRef,
scroll,
data: visibleNodes,
});

const setActived = usePersistFn(
(
Expand Down Expand Up @@ -212,14 +224,18 @@ const Tree = forwardRef((props: TreeProps, ref: React.Ref<TreeInstanceFunctions>

const renderEmpty = () => parseTNode(empty, null, emptyText);

const renderItems = () => {
if (visibleNodes.length <= 0) {
const renderItems = (renderNode: TreeNode[]) => {
if (renderNode.length <= 0) {
return renderEmpty();
}

return (
<TransitionGroup name={transitionNames.treeNode} className={treeClassNames.treeList}>
{visibleNodes.map((node, index) => (
<TransitionGroup
name={transitionNames.treeNode}
className={treeClassNames.treeList}
style={isVirtual ? { ...treeNodeStyle } : null}
>
{renderNode.map((node, index) => (
// https://github.com/reactjs/react-transition-group/issues/668
<CSSTransition
nodeRef={nodeList[index]}
Expand All @@ -229,7 +245,7 @@ const Tree = forwardRef((props: TreeProps, ref: React.Ref<TreeInstanceFunctions>
>
<TreeItem
ref={nodeList[index]}
node={node}
node={store.getNode(node.value)}
empty={empty}
icon={icon}
label={label}
Expand All @@ -242,6 +258,8 @@ const Tree = forwardRef((props: TreeProps, ref: React.Ref<TreeInstanceFunctions>
disableCheck={disableCheck}
onClick={handleItemClick}
onChange={handleChange}
onTreeItemMounted={handleRowMounted}
isVirtual={true}
/>
</CSSTransition>
))}
Expand All @@ -266,9 +284,19 @@ const Tree = forwardRef((props: TreeProps, ref: React.Ref<TreeInstanceFunctions>
[treeClassNames.treeCheckable]: checkable,
[treeClassNames.treeFx]: transition,
[treeClassNames.treeBlockNode]: expandOnClickNode,
[`${treeClassNames.tree}__vscroll`]: isVirtual,
})}
style={style}
ref={treeRef}
>
{renderItems()}
{isVirtual ? (
<>
<div className={`${treeClassNames.tree}__vscroll-cursor`} style={cursorStyle} />
{renderItems(visibleData)}
</>
) : (
renderItems(visibleNodes)
)}
</div>
</TreeDraggableContext.Provider>
);
Expand Down
Loading

0 comments on commit 8d65442

Please sign in to comment.