import React, { useRef, useContext, useEffect, useMemo } from "react"; import { VariableSizeList, ListOnScrollProps, ListChildComponentProps, } from "react-window"; import classNames from "classnames"; import { forwardRefWithStatics } from "../_util/forward-ref-with-statics"; import { mergeRefs } from "../_util/merge-refs"; import { TreeData, TreeProps } from "./TreeProps"; import { TreeNode, TreeNodeInnerProps } from "./TreeNode"; import { TreeContext } from "./TreeContext"; import { useConfig } from "../_util/config-context"; type InnerTreeData = TreeData & TreeNodeInnerProps; const defaultItemHeight = 32; export interface VirtualizedTreeListProps { /** * 树节点 */ nodes?: InnerTreeData[]; /** * 列表高 */ height?: TreeProps["height"]; /** * 滚动到底部事件 */ onScrollBottom?: (props: ListOnScrollProps) => void; /** * 虚拟滚动组件 ref */ virtualizedRef?: React.Ref; } function Row({ data, index, style }: ListChildComponentProps) { const { classPrefix } = useConfig(); const { activable, activeIds } = useContext(TreeContext); const { pure, style: nodeStyle = {}, className, ...props } = data[index]; return (
  • ); } export const VirtualizedTreeList = forwardRefWithStatics( function VirtualizedList( { nodes, height: listHeight, onScrollBottom, virtualizedRef, }: VirtualizedTreeListProps, ref: React.Ref ) { const vRef = useRef(null); const listRef = useRef(null); // VariableSizeList 默认按 index 缓存 style,需手动触发重置 useEffect(() => { if (vRef.current) { nodes.forEach((_, i) => vRef.current.resetAfterIndex(i, true)); } }, [nodes]); const [minHeight, maxHeight] = Array.isArray(listHeight) ? listHeight : [listHeight, listHeight]; const itemsSize = useMemo( () => nodes.reduce( (p, _, i) => p + (nodes[i].height || defaultItemHeight), 0 ), [nodes] ); const height = useMemo(() => { if (typeof minHeight === "number" && minHeight > itemsSize) { return minHeight; } if (typeof maxHeight === "number" && maxHeight < itemsSize) { return maxHeight; } if ( typeof minHeight === "number" && typeof maxHeight === "number" && minHeight < itemsSize && maxHeight > itemsSize ) { return itemsSize; } return maxHeight ?? itemsSize; }, [itemsSize, maxHeight, minHeight]); function handleScroll(props: ListOnScrollProps) { if (listRef.current && onScrollBottom) { if (typeof maxHeight !== "number" || itemsSize > maxHeight) { const { scrollHeight, scrollTop, clientHeight } = listRef.current; if (scrollHeight <= Math.ceil(clientHeight + scrollTop + 1)) { onScrollBottom(props); } } } } return ( { return nodes[index].height || defaultItemHeight; }} innerElementType="ul" onScroll={handleScroll} itemKey={index => nodes[index].pathKey || nodes[index].id} itemData={nodes} > {Row} ); }, { displayName: "VirtualizedList", } );