import React, { useMemo, useContext, forwardRef, useCallback, useEffect, } from "react"; import classNames from "classnames"; import { TreeNode, TreeNodeInnerProps } from "./TreeNode"; import { CheckTree, CheckTreeRelation } from "../checktree"; import { TreeContext } from "./TreeContext"; import { useDefault } from "../_util/use-default"; import { isChildOfType } from "../_util/is-child-of-type"; import { useConfig } from "../_util/config-context"; import { forwardRefWithStatics } from "../_util/forward-ref-with-statics"; import { VirtualizedTreeList } from "./VirtualizedTreeList"; import { TreeNodeEventData, TreeData, TreeProps } from "./TreeProps"; import { Tree as DeprecatedTree } from "./deprecated/Tree"; import { checkTreeNode, getDefaultExpandedIds, getFilteredExpandedIds, getNodeShowState, } from "./util"; import { noop } from "../_util/noop"; type InnerTreeData = TreeData & TreeNodeInnerProps; interface TreeInfo { nodes: InnerTreeData[]; relations: CheckTreeRelation; disabledIds: string[]; } /** * 构造树节点 */ function renderTree( data: TreeData[], level: number, pid?: string, pPathKey?: string, // 添加过滤函数 filter?: (node: TreeData) => boolean ): TreeInfo { const nodes: InnerTreeData[] = []; const relations: CheckTreeRelation = {}; const disabledIds: string[] = []; data.forEach(({ children, id, disableSelect, ...props }) => { const pathKey = pid ? `${pPathKey}$${id}` : id; // isFiltered 是否是过滤剩下的 const isFiltered = filter ? filter({ ...props, children, id, disableSelect }) : true; nodes.push({ ...props, isFiltered, level, id, children, disableSelect, pathKey, }); if (pid) { relations[id] = pid; } if (disableSelect) { disabledIds.push(id); } if (children && children.length > 0) { const childInfo = renderTree(children, level + 1, id, pathKey, filter); // 合并子级信息 childInfo.nodes.forEach(i => nodes.push(i)); childInfo.disabledIds.forEach(i => disabledIds.push(i)); Object.entries(childInfo.relations).forEach(([key, value]) => { relations[key] = value; }); } }); return { nodes, relations, disabledIds }; } /** * TreeNode -> Data */ function getTreeData(children: React.ReactNode): TreeData[] { const data: TreeData[] = []; React.Children.forEach(children, child => { if (isChildOfType(child, TreeNode)) { const props = { ...child.props, children: child.props.children ? getTreeData(child.props.children) : null, }; data.push(props); } }); return data; } export const Tree = forwardRefWithStatics( function Tree(props: TreeProps, ref: React.Ref) { const isValid = useMemo(() => checkTreeNode(props.children), [ props.children, ]); if (!isValid) { return ; } return ; }, { Node: TreeNode, } ); Tree.displayName = "Tree"; const TreeInner = forwardRef(function TreeInner( { data, children, selectable, selectedIds, defaultSelectedIds = [], selectStrictly, onSelect, selectValueMode = "all", fullActivable, activable = fullActivable, activeIds, defaultActiveIds = [], onActive, expandedIds, defaultExpandedIds = [], onExpand = noop, fullExpandable, defaultExpandAll, defaultExpandParent = true, forceExpandParent, onLoad, onLoadError, switcherIcon, className, height, filterState, filter, ...props }: TreeProps, ref: React.Ref ) { const { nodes, relations, disabledIds } = useMemo( () => renderTree(data || getTreeData(children), 0, "", "", filter), [children, data, filter] ); // 选择关系和结构关系可能不同 const selectRelations = useMemo(() => (selectStrictly ? {} : relations), [ relations, selectStrictly, ]); const { classPrefix } = useConfig(); const [selectedNodes, setSelectedNodes] = useDefault( selectedIds, defaultSelectedIds, onSelect ); const [activeNodes, setActiveNodes] = useDefault( activeIds, defaultActiveIds, onActive ); const [rawExpandedNodes, setExpandedNodes] = useDefault( expandedIds, getDefaultExpandedIds({ relations, expandedIds, defaultExpandedIds, defaultExpandAll, defaultExpandParent, forceExpandParent, }), onExpand ); const expandedNodes = useMemo(() => { if (forceExpandParent) { const idSet = new Set(rawExpandedNodes); rawExpandedNodes.forEach(id => { let child = id; while (child in relations && !idSet.has(relations[child])) { idSet.add(relations[child]); child = relations[child]; } }); return [...idSet]; } return rawExpandedNodes; }, [forceExpandParent, rawExpandedNodes, relations]); const visibleNodes = useMemo(() => { return nodes.filter(({ id, isFiltered }) => getNodeShowState(id, relations, expandedNodes, isFiltered) ); }, [expandedNodes, nodes, relations]); // 处理过滤 useEffect(() => { if (filterState && typeof filter === "function") { const expandedIds = getFilteredExpandedIds(nodes, relations); setExpandedNodes([...expandedIds]); } }, [filterState, filter, nodes, relations]); const [List, ListProps] = useMemo( () => (height ? [VirtualizedTreeList, { height }] : [TreeList, {}]), [height] ); const handleActive = useCallback( (activeId: string, event: React.MouseEvent, data: TreeNodeEventData) => setActiveNodes([activeId], { active: true, nodeId: activeId, event, data, }), [setActiveNodes] ); const handleExpand = useCallback( ( nodeId: string, expanded: boolean, event: React.MouseEvent, data: TreeNodeEventData ) => setExpandedNodes( rawExpandedNodes => { let expandedNodes = rawExpandedNodes || []; if (forceExpandParent) { const idSet = new Set(rawExpandedNodes); rawExpandedNodes.forEach(id => { let child = id; while (child in relations && !idSet.has(relations[child])) { idSet.add(relations[child]); child = relations[child]; } }); expandedNodes = [...idSet]; } return expanded ? [...expandedNodes, nodeId] : expandedNodes.filter(id => id !== nodeId); }, { nodeId, expanded, event, data } ), [forceExpandParent, relations, setExpandedNodes] ); const handleSelectChange = useCallback( (value, { check, event }) => setSelectedNodes(value, { nodeId: check.name, selected: check.value, event, }), [setSelectedNodes] ); return (
{selectable ? ( {visibleNodes} ) : ( {visibleNodes} )}
); }); TreeInner.displayName = "TreeInner"; function TreeList({ nodes }: { nodes: InnerTreeData[] }) { const { classPrefix } = useConfig(); const { activable, activeIds } = useContext(TreeContext); return (
    {nodes.map( ({ pure, style = {}, className, height, pathKey, ...props }) => (
  • ) )}
); }