# Tree 树

- 来源：[E10 UI 组件库](https://www.e-cology.com.cn/ui/tree/freepass)
- 平台：PC
- 维护者：王斌
- 设计稿：http://pd.e-cology.com.cn/pd/E10/interface/e10xiaoguotu/PC/E10zjys/#s23
- Issue：http://gitlab.ecology10.cn/FRONTEND/weapp-ui/-/issues?state=all&search=[Tree]

## 代码演示

### 基础用法

直接引入组件进行使用

```jsx
import React from 'react';
import { Tree, ITreeData } from '@weapp/ui';

const treeData = [{
  id: 1,
  content: '根节点',
  title:'这是根节点',
  isLeaf: false,
  children: [{
    id: 2,
    content: '一级-1',
    isLeaf:false,
    children: [{
      isLeaf: false,
      id: 3,
      content: '二级-1',
      children: [
        {
          isLeaf: true,
          id: 31,
          content: '三级-11',
        },
        {
          isLeaf: true,
          id: 32,
          content: '三级-22',
        },
        {
          isLeaf: true,
          id: 33,
          content: '三级-33',
        },
      ]
    }, {
      isLeaf: true,
      id: 4,
      content: '二级-2'
    }, {
      isLeaf: true,
      id: 5,
      content: '二级-3'
    }]
  }, {
    isLeaf: true,
    id: 6,
    content: '一级-2'
  }]
}];

export default class BasicDemo extends React.Component{
  state = {
    selectedKeys: []
  }
  onSelect = (data: ITreeData, target: HTMLSpanElement) => {
    this.setState({
      selectedKeys: [data.id]
    })
  }

  render() {
    return (
      <Tree
            data={treeData}
            onSelect={this.onSelect}
            selectedKeys={this.state.selectedKeys}
      />
    );
  }
}
```

### 自定义图标展示

```jsx
import React from 'react';
import { Icon, Tree, ITreeData } from '@weapp/ui';

export default class DirectoryDemo extends React.Component{

  renderIcon = (root: ITreeData, isParent: boolean, isExpanded: boolean) => {
    console.log(root, isParent, isExpanded)
    return <Icon name={isParent ? 'Icon-gateway-o' : 'Icon-customer-o'}  />
  }

  render() {
    const treeData = [{
      id: 1,
      content: '根节点',
      isLeaf: false,
      children: [{
        id: 2,
        content: '一级-1',
        isLeaf:false,
        children: [{
          isLeaf: true,
          id: 3,
          content: '二级-1'
        }, {
          isLeaf: true,
          id: 4,
          content: '二级-2'
        }, {
          isLeaf: true,
          id: 5,
          content: '二级-3'
        }]
      }, {
        isLeaf: true,
        id: 6,
        content: '一级-2'
      }]
    }];

    return (
      <Tree data={treeData} renderIcon={this.renderIcon} />
    );
  }
}
```

### 展开受控

展开受控

```jsx
import React from 'react';
import { DemoState } from './types';
import { ITreeData, Tree, TreeRootIdArray } from '@weapp/ui';

export default class ControlDemo extends React.Component<{}, DemoState>{
  constructor(props: any) {
    super(props);
    this.state = {
      treeData: [{
        id: 1,
        content: '根节点',
        isLeaf: false,
        children: [{
          id: 2,
          content: '一级-1',
          isLeaf:false,
          children: [{
            isLeaf: true,
            id: 3,
            content: '二级-1'
          }, {
            isLeaf: true,
            id: 4,
            content: '二级-2'
          }, {
            isLeaf: true,
            id: 5,
            content: '二级-3'
          }]
        }, {
          isLeaf: true,
          id: 6,
          content: '一级-2'
        }]
      }],
      expandedKeys: [1]
    }
  }
  onExpand = (v: TreeRootIdArray, root: ITreeData, config: { isExpanded: boolean }) => {
    console.log('onExpand:', v, root, config.isExpanded)
    this.setState({ expandedKeys: v })
  }

  onSearch = (searchValue: string | number, findRoot?: ITreeData[], expandedKeys?: TreeRootIdArray) => {
    console.log(searchValue, findRoot, expandedKeys)
    this.setState({ expandedKeys })
  }

  render() {
    const { treeData, expandedKeys } = this.state;

    return (
      <Tree
        data={treeData}
        expandedKeys={expandedKeys}
        onExpand={this.onExpand}
      />
    );
  }
}
```

### 选中受控

选中受控

```jsx
import React from 'react';
import { DemoState } from './types';
import { Tree, TreeRootIdArray, ITreeOncheckHelpInfo, Switch, SwitchChangeEventHandler } from '@weapp/ui';
import { datas } from './mock';

export default class CheckedDemo extends React.Component<{}, DemoState>{
  constructor(props: any) {
    super(props);
    this.state = {
      treeData: [{
        id: 1,
        content: '根节点',
        isLeaf: false,
        children: [{
          id: 2,
          content: '一级-1',
          isLeaf: false,
          children: [{
            isLeaf: false,
            id: 3,
            content: '二级-1',
            children: [{
              isLeaf: true,
              id: 13,
              content: '三级-1',
            }]
          }, {
            isLeaf: false,
            id: 4,
            content: '二级-2',
            children: [{
              isLeaf: true,
              id: 14,
              content: '三级-2',
            }, {
              isLeaf: true,
              id: 15,
              content: '三级-3',
            }, {
              isLeaf: true,
              id: 16,
              content: '三级-4',
            }]
          }, {
            isLeaf: true,
            id: 5,
            content: '二级-3'
          }]
        }, {
          isLeaf: true,
          id: 6,
          content: '一级-2'
        }]
      }],
      checkedKeys: [],
      checked: false,
      checkPosition: 'default'
    }
  }

  handleChange: SwitchChangeEventHandler = (checked, e) => {
    this.setState({
      checked,
      checkPosition: checked ? 'right' : 'default'
    })
  }

  onTreeCheck = (v: TreeRootIdArray, b: ITreeOncheckHelpInfo, config: object) => {
    console.log('onCheck:', v, b, config)
    this.setState({ checkedKeys: v })
  }

  // 控制节点是否可选，以及是否显示check框
  isCheckboxDisabled = (node: any) => {
    const { disabledCheck } = node || {};

    const disabledProps: any = {
      disabled: disabledCheck,
      hidden: disabledCheck,
    };

    // 如果开启包含下级
    // if (showAllBranch && disabledCheck && node.isParent) {
    //   disabledProps.disabled = false;
    // }

    return disabledProps;
  };
  render() {
    const { treeData, checkedKeys, checked, checkPosition } = this.state;

    return (
      <div>
        <div style={{ margin: '15px 10px', float: 'right', display: 'flex', alignItems: 'center' }}><Switch value={checked} onChange={this.handleChange} />&nbsp;&nbsp;&nbsp;选择框位置</div>
        <Tree
          data={treeData}
          checkPosition={checkPosition}
          checkable
          checkedKeys={checkedKeys}
          onCheck={this.onTreeCheck}
          disabledCheckedKeys={[6, 14, 15]}

        />
      </div>
    );
  }
}
```

### 自定义渲染

自定义渲染

```jsx
import React from "react";
import { ITreeData, Tree } from "@weapp/ui";

const treeData = [
  {
    id: 1,
    content: "根节点",
    isLeaf: false,
    children: [
      {
        id: 2,
        content: "一级-1",
        isLeaf: false,
        children: [
          {
            isLeaf: false,
            id: 3,
            content: "二级-1",
            children: [
              {
                isLeaf: true,
                id: 34,
                content: "三级-4",
              },
              {
                isLeaf: true,
                id: 35,
                content: "三级-5",
              },
              {
                isLeaf: true,
                id: 36,
                content: "三级-6",
              },
            ]
          },
          {
            isLeaf: true,
            id: 4,
            content: "二级-2",
          },
          {
            isLeaf: true,
            id: 5,
            content: "二级-3",
          },
          {
            isLeaf: true,
            id: 6,
            content: "二级-4",
          },
          {
            isLeaf: true,
            id: 7,
            content: "二级-5",
          },
          {
            isLeaf: true,
            id: 8,
            content: "二级-6",
          },
          {
            isLeaf: true,
            id: 9,
            content: "二级-7",
          },
          {
            isLeaf: true,
            id: 10,
            content: "二级-8",
          },
          {
            isLeaf: true,
            id: 11,
            content: "二级-9",
          },
        ],
      },
      {
        isLeaf: true,
        id: 99,
        content: "一级-2",
      },
    ],
  },
];
export default class CustomRenderDemo extends React.Component {
  customRender = (data: ITreeData) => {
    return data.id < 6 && data.id > 2 ? (
      <span style={{ fontSize: 16, margin: 0, lineHeight: 1 }}>
        {data.content}
        <small style={{ display: "block", fontSize: 10 }}>自定义内容</small>
      </span>
    ) : (
      <div>{data.content}</div>
    )
  };

  render() {
    return (
      <Tree
       
        data={treeData}
        virtualMode
        virtualContainerHeight={400}
        customRenderNode={this.customRender}
      />
    );
  }
}
```

### 带连线的树形

```jsx
import React from 'react';
import { Tree, ITreeData } from '@weapp/ui';

const treeData = [{
  id: '1',
  content: '根节点1',
  title:'这是根节点1',
  isLeaf: false,
  children: [{
    id: '1-1',
    content: '一级-1',
    isLeaf:false,
    children: [{
      isLeaf: false,
      id: '1-1-1',
      content: '二级-1',
      children: [
        {
          isLeaf: true,
          id: '1-1-1-1',
          content: '三级-1',
        },
        {
          isLeaf: true,
          id: '1-1-1-2',
          content: '三级-2',
        },
        {
          isLeaf: true,
          id: '1-1-1-3',
          content: '三级-3',
        },
      ]
    }, {
      isLeaf: true,
      id: '1-1-2',
      content: '二级-2'
    }, {
      isLeaf: true,
      id: '1-1-3',
      content: '二级-3'
    }]
  }, {
    isLeaf: true,
    id: '1-2',
    content: '一级-2'
  }]
}, {
    id: '2',
    content: '根节点2',
    title:'这是根节点',
    isLeaf: true,
}];

export default class BasicDemo extends React.Component{
  state = {
    selectedKeys: []
  }
  onSelect = (data: ITreeData, target: HTMLSpanElement) => {
    this.setState({
      selectedKeys: [data.id]
    })
  }

  render() {
    return (
      <Tree 
            data={treeData}
            onSelect={this.onSelect}
            selectedKeys={this.state.selectedKeys}
            showLine={true}
      />
    );
  }
}
```

### 搜索

带搜索树形控件

```jsx
import React from 'react';
import { Tree, ITreeData, TreeRootIdArray } from '@weapp/ui';

const treeData = [{
  id: 1,
  content: '根节点',
  title:'这是根节点',
  isLeaf: false,
  children: [{
    id: 2,
    content: '一级-1',
    isLeaf:false,
    children: [{
      isLeaf: false,
      id: 3,
      content: '二级-1a',
      children: [
        {
          isLeaf: true,
          id: 31,
          content: '三级-11abc',
        },
        {
          isLeaf: true,
          id: 32,
          content: '三级-22',
        },
        {
          isLeaf: true,
          id: 33,
          content: '三级-33',
        },
      ]
    }, {
      isLeaf: true,
      id: 4,
      content: '二级-2A'
    }, {
      isLeaf: true,
      id: 5,
      content: '二级-3'
    }]
  }, {
    isLeaf: true,
    id: 6,
    content: '一级-2'
  }]
}];

export default class SearchDemo extends React.Component{
  state = {
    searchValue: ''
  }

  onSearch = (searchValue: string | number, findRoot?: ITreeData[], expandedKeys?: TreeRootIdArray) => {
    console.log(searchValue, findRoot, expandedKeys)
    this.setState({ expandedKeys, searchValue })
  }

  render() {
    return (
      <Tree
            data={treeData}
            needSearch={true}
            searchLabel='搜索'
            onSearch={this.onSearch}
            searchValue={this.state.searchValue}
            caseSensitive={false}
      />
    );
  }
}
```

### 虚拟树搜索

带搜索功能的虚拟树形控件

```jsx
import React from 'react';
import { Tree, ITreeData, TreeRootIdArray } from '@weapp/ui';
import { datas } from "./mock";

export default class SearchDemo extends React.Component{
  state = {
    searchValue: ''
  }

  onSearch = (searchValue: string | number, findRoot?: ITreeData[], expandedKeys?: TreeRootIdArray) => {
    this.setState({ expandedKeys, searchValue })
  }

  render() {
    return (
      <Tree
            data={datas}
            needSearch={true}
            searchLabel='搜索'
            onSearch={this.onSearch}
            searchValue={this.state.searchValue}
            caseSensitive={false}
            virtualMode
            virtualContainerHeight={'600px'}
      />
    );
  }
}
```

### 异步加载

异步加载

```jsx
import React from 'react';
import { Tree, ITreeData } from '@weapp/ui';
import { DemoState } from './types';

const fetchData = (data: ITreeData, callback: any) => {
  setTimeout(() => {
    const mockAsyncDats = [{
      isLeaf: true,
      id: 13,
      content: '三级-1'
    }, {
      isLeaf: true,
      id: 14,
      content: '三级-2'
    }, {
      isLeaf: true,
      id: 15,
      content: '三级-3'
    }];
    callback(mockAsyncDats);
  }, 200);
}

export default class AsyncDemo extends React.Component<{}, DemoState>{
  constructor(props: any) {
    super(props);
    this.state = {
      treeData: [{
        id: 1,
        content: '根节点',
        isLeaf: false,
        children: [{
          id: 2,
          content: '一级-1',
          isLeaf: false,
          children: [{
            isLeaf: false,
            id: 3,
            content: '二级-1'
          }, {
            isLeaf: true,
            id: 4,
            content: '二级-2'
          }, {
            isLeaf: true,
            id: 5,
            content: '二级-3'
          }]
        }, {
          isLeaf: true,
          id: 6,
          content: '一级-2'
        }]
      }],
    }
  }

  loadMore = (data: ITreeData) => new Promise((resolve) => {
    const { treeData } = this.state;
    let [ ...newTreeData ] = treeData;
    fetchData(data, (resData: ITreeData) => {
      const childrenData = resData.map((item: any) => ({
        ...item,
        parentId: data.id
      }));

      function getNewTreeData(datas:ITreeData[]) {
        datas.forEach((item) => {
          if (item.id === data.id) {
            item.children = childrenData
          } else if (item.children && item.children.length) {
            getNewTreeData(item.children)
          }
        })
      }

      getNewTreeData(newTreeData)
      this.setState({ treeData: [...newTreeData] });
      resolve(data);
    });
  });

  render() {
    const { treeData } = this.state;

    return (
      <Tree 
        data={treeData} 
        isAsync 
        loadMore={this.loadMore}
      />
    );
  }
}
```

### 定制化key

定制化key

```jsx
import React from 'react';
import { Tree } from '@weapp/ui';

export default class RenderKeyDemo extends React.Component{
  render() {
    const treeData = [{
      key: 1,
      name: '根节点',
      isLeaf: false,
      childs: [{
        key: 2,
        name: '一级-1',
        isLeaf:false,
        childs: [{
          isLeaf: true,
          key: 3,
          name: '二级-1'
        }, {
          isLeaf: true,
          key: 4,
          name: '二级-2'
        }, {
          isLeaf: true,
          key: 5,
          name: '二级-3'
        }]
      }, {
        isLeaf: true,
        key: 6,
        name: '一级-2'
      }]
    }];

    const renderKey = {
      dataKey: 'key',
      dataContent: 'name',
      dataChildren: 'childs',
    }

    return (
      <Tree
        data={treeData}
        renderKey={renderKey}
      />
    );
  }
}
```

### 节点拖拽

支持多种拖拽位置（before、after、inside）和自定义拖拽行为

```jsx
import React from 'react';
import { Tree, ITreeData } from '@weapp/ui';
import { TreeDropInfo } from '../../../../components/tree/types';

interface TreeDemoState {
  treeData: ITreeData[];
}

interface TreeDemoProps {}

class TreeDemo extends React.Component<TreeDemoProps, TreeDemoState> {
  constructor(props: TreeDemoProps) {
    super(props);

    this.state = {
      treeData: [
        {
          id: '1',
          title: '系统管理',
          isLeaf: false,
          children: [
            {
              id: '1-1',
              title: '用户管理',
              parentId: '1',
              isLeaf: true,
            },
            {
              id: '1-2',
              title: '角色管理',
              parentId: '1',
              isLeaf: true,
            },
            {
              id: '1-3',
              title: '权限管理',
              parentId: '1',
              isLeaf: true,
            }
          ]
        },
        {
          id: '2',
          title: '业务配置',
          isLeaf: false,
          children: [
            {
              id: '2-1',
              title: '流程配置',
              parentId: '2',
              isLeaf: true,
            }
          ]
        }
      ]
    };
  }

  // 拖拽处理函数
  handleTreeDrop = (dropInfo: TreeDropInfo) => {
    const { dragNode, dropNode, position, newTreeData } = dropInfo
    console.log('拖拽详情', {
      dragNode, dropNode, position, newTreeData
    });

    // 更新树数据
    this.setState({
      treeData: newTreeData
    });
  };

  // 拖拽校验：是否可以拖拽
  canDragNode = (node: ITreeData) => {
    // 禁止拖拽某些特定节点
    return !node.disabledDrop && node.id !== '1';
  };

  // 拖拽校验：是否可以放置
  canDropNode = (
    dragNode: ITreeData,
    dropNode: ITreeData
  ) => {
    // 不允许将节点拖到自身或其子节点
    return dragNode.id !== dropNode.id &&
      !this.isDescendant(dragNode, dropNode);
  };

  // 拖拽校验：是否可以放置
  canDropTreeNode = (node: ITreeData) => {
    // 该节点不允许被拖拽放置进入
    return node.id !== '1';
  };

  // 辅助函数：判断是否为子孙节点
  isDescendant = (
    dragNode: ITreeData,
    dropNode: ITreeData
  ): boolean => {
    const findDescendant = (node: ITreeData): boolean => {
      if (!node.children) return false;

      return node.children.some((child: any) =>
        child.id === dropNode.id ||
        findDescendant(child)
      );
    };

    return findDescendant(dragNode);
  };

  onMouseEnter = (node: ITreeData, e: any) => {
    console.log('onMouseEnter', node, e);
  }

  customInsideOffset = () => {
    return 40
  }

  render() {
    return (
      <div>
        <Tree
         
          data={this.state.treeData}
          dragDropConfig={{
            // 是否启用拖拽
            draggable: true,
            // 自定义拖拽校验
            canDrag: this.canDragNode,
            // 自定义放置校验 通过拖拽节点和放置节点之间的关系进行校验
            canDrop: this.canDropNode,
            // 自定义放置节点校验 通过节点自身信息校验是否可以被拖拽放置进入
            canDropTreeNode: this.canDropTreeNode,
            // 拖拽完成回调
            onDrop: this.handleTreeDrop,
            customInsideOffset: this.customInsideOffset
          }}
          renderKey={{ dataContent: 'title' }}
          onMouseEnter={this.onMouseEnter}
        />
      </div>
    );
  }
}

export default TreeDemo;
```

### 加载更多(正常模式和虚拟模式对比)

左边正常模式单节点加载2k条数据，右边虚拟模式单节点加载1w条数据

```jsx
import React from 'react';
import { Tree, ITreeData, appendDataFromNode, TreeRootIdArray } from '@weapp/ui';

export default class BasicDemo extends React.Component {
  state = {
    expandedKeys: [1, 2, 3, 6],
    currentLoadNodeId: '',
    loading: false,
    pageInfo: [
      { id: 1, hasMore: false, pageNum: 1 },
      { id: 3, hasMore: true, pageNum: 1 },
      { id: 6, hasMore: true, pageNum: 1 },
    ],
    treeData: [{
      id: 1,
      content: '根节点',
      title: '这是根节点',
      isLeaf: false,
      children: [{
        id: 2,
        content: '一级-1',
        isLeaf: false,
        children: [{
          isLeaf: false,
          id: 3,
          content: '二级-1',
          children: [
            {
              isLeaf: true,
              id: 31,
              content: '三级-11',
            }
          ]
        }, {
          isLeaf: true,
          id: 4,
          content: '二级-2'
        }, {
          isLeaf: true,
          id: 5,
          content: '二级-3'
        }]
      },
      {
        isLeaf: false,
        id: 6,
        content: '一级-2',
        children: [
          {
            isLeaf: false,
            id: 61,
            content: '61'
          },
          {
            isLeaf: false,
            id: 62,
            content: '62'
          }
        ]
      }]
    }],
    expandedKeysV: [1, 2, 3, 6],
    currentLoadNodeIdV: '',
    loadingV: false,
    pageInfoV: [
      { id: 1, hasMore: false, pageNum: 1  },
      { id: 3, hasMore: true, pageNum: 1  },
      { id: 6, hasMore: true, pageNum: 1  },
    ],
    treeDataV: [{
      id: 1,
      content: '根节点',
      title: '这是根节点',
      isLeaf: false,
      children: [{
        id: 2,
        content: '一级-1',
        isLeaf: false,
        children: [{
          isLeaf: false,
          id: 3,
          content: '二级-1',
          children: [
            {
              isLeaf: true,
              id: 31,
              content: '三级-11',
            }
          ]
        }, {
          isLeaf: true,
          id: 4,
          content: '二级-2'
        }, {
          isLeaf: true,
          id: 5,
          content: '二级-3'
        }]
      },
      {
        isLeaf: false,
        id: 6,
        content: '一级-2',
        children: [
          {
            isLeaf: false,
            id: 61,
            content: '61'
          },
          {
            isLeaf: false,
            id: 62,
            content: '62'
          }
        ]
      }]
    }]
  }

  handleLoadMore = (id: number | string, data: ITreeData) => {
    this.setState({
      loading: true,
      currentLoadNodeId: id
    })
    setTimeout(() => {
      const { pageInfo, treeData } = this.state
      const pageInfoData = pageInfo.map((item) => {
        if (item.id === id) item.hasMore = !item.hasMore
        return item
      })
      let arr = []
      for (let i = 0; i < 2000; i++) {
        arr[i] = {
          isLeaf: true,
          id: `${id}-${i}`,
          content: `${id}-${i}`,
        }
      }
      const newData = appendDataFromNode(id, treeData, arr)
      this.setState({
        pageInfo: pageInfoData,
        treeData: newData,
        loading: false
      })
    }, 0);
  }

  handleLoadMoreV = (id: number | string, data: ITreeData) => {
    this.setState({
      loadingV: true,
      currentLoadNodeIdV: id
    })
    setTimeout(() => {
      const { pageInfoV, treeDataV } = this.state
      // const pageInfoData = pageInfoV.map((item) => {
      //   if (item.id === id) item.hasMore = !item.hasMore
      //   return item
      // })
      let arr = []
      for (let i = 0; i < 10000; i++) {
        arr[i] = {
          isLeaf: true,
          id: `${id}-${i}`,
          content: `${id}-${i}`,
        }
      }
      const newData = appendDataFromNode(id, treeDataV, arr)
      this.setState({
        // pageInfoV: pageInfoData,
        treeDataV: newData,
        loadingV: false
      })
    }, 0);
  }

  onExpand = (v: TreeRootIdArray, root: ITreeData, config: { isExpanded: boolean }) => {
    this.setState({ expandedKeys: v })
  }

  onExpandV = (v: TreeRootIdArray, root: ITreeData, config: { isExpanded: boolean }) => {
    this.setState({ expandedKeysV: v })
  }

  render() {
    const { expandedKeys, pageInfo, treeData, loading, currentLoadNodeId, expandedKeysV, pageInfoV, treeDataV, loadingV, currentLoadNodeIdV } = this.state
    return (
      <div style={{ width: '100%', height: '500px', display: 'flex' }}>
        <div style={{ width: '300px' }}>
          <Tree
            data={treeData}
            expandedKeys={expandedKeys}
            onExpand={this.onExpand}
            pageInfo={pageInfo}
            onLoadMore={this.handleLoadMore}
            loading={loading}
            currentLoadNodeId={currentLoadNodeId}
          />
        </div>
        <div style={{ width: '300px' }}>
          <Tree 
            data={treeDataV}
            expandedKeys={expandedKeysV}
            onExpand={this.onExpandV}
            pageInfo={pageInfoV}
            onLoadMore={this.handleLoadMoreV}
            loading={loadingV}
            currentLoadNodeId={currentLoadNodeIdV}
            virtualMode
            virtualContainerHeight={500}
          />
        </div>
      </div>
    );
  }
}
```

### 异步加载 + 加载更多 + 虚拟列表

三种场景综合使用

```jsx
import React from 'react';
import { Tree, ITreeData, appendDataFromNode } from '@weapp/ui';

const fetchData = (data: ITreeData, callback: any) => {
  setTimeout(() => {
    const mockAsyncDats = [{
      isLeaf: true,
      id: 13,
      content: '三级-1'
    }, {
      isLeaf: true,
      id: 14,
      content: '三级-2'
    }, {
      isLeaf: true,
      id: 15,
      content: '三级-3'
    }];
    callback(mockAsyncDats);
  }, 200);
}

export default class AsyncDemo extends React.Component<{}, any>{
  constructor(props: any) {
    super(props);
    this.state = {
      treeData: [{
        id: 1,
        content: '根节点',
        isLeaf: false,
        children: [{
          id: 2,
          content: '一级-1',
          isLeaf: false,
          children: [{
            isLeaf: false,
            id: 3,
            content: '二级-1'
          }, {
            isLeaf: true,
            id: 4,
            content: '二级-2'
          }, {
            isLeaf: true,
            id: 5,
            content: '二级-3'
          }]
        }, {
          isLeaf: false,
          id: 6,
          content: '一级-2',
          children: [{
            isLeaf: false,
            id: 7,
            content: '二级-1'
          }]
        }]
      }],
      expandedKeys: [1, 2, 3, 6],
      currentLoadNodeId: '',
      loading: false,
      pageInfo: [
        { id: 1, hasMore: false, pageNum: 1 },
        { id: 3, hasMore: true, pageNum: 1 },
      ],
    }
  }

  loadMore = (data: ITreeData) => new Promise((resolve) => {
    const { treeData } = this.state;
    let [...newTreeData] = treeData;
    fetchData(data, (resData: ITreeData) => {
      const childrenData = resData.map((item: any) => ({
        ...item,
        parentId: data.id
      }));

      function getNewTreeData(datas: ITreeData[]) {
        datas.forEach((item) => {
          if (item.id === data.id) {
            item.children = childrenData
          } else if (item.children && item.children.length) {
            getNewTreeData(item.children)
          }
        })
      }

      getNewTreeData(newTreeData)
      this.setState({
        treeData: [...newTreeData],
        pageInfo: [...this.state.pageInfo, { id: 2, hasMore: true }]
      });
      resolve(data);
    });
  });

  handleLoadMore = (id: number | string, data: ITreeData, pageNum?: number | string) => {
    this.setState({
      loading: true,
      currentLoadNodeId: id
    })
    setTimeout(() => {
      const { pageInfo, treeData } = this.state
      // const pageInfoData = pageInfo.map((item: any) => {
      //   if (item.id === id) item.hasMore = !item.hasMore
      //   return item
      // })
      let arr = []
      for (let i = 0; i < 10; i++) {
        arr[i] = {
          isLeaf: true,
          id: `${id}-${i}`,
          content: `${id}-${i}`,
        }
      }
      const newData = appendDataFromNode(id, treeData, arr)
      this.setState({
        // pageInfo: pageInfoData,
        treeData: newData,
        loading: false
      })
    }, 0);
  }

  render() {
    const { treeData, pageInfo, loading, currentLoadNodeId } = this.state;

    return (
      <Tree
        data={treeData}
        isAsync
        loadMore={this.loadMore}
        virtualMode
        pageInfo={pageInfo}
        onLoadMore={this.handleLoadMore}
        loading={loading}
        currentLoadNodeId={currentLoadNodeId}
        virtualContainerHeight={400}
      />
    );
  }
}
```

### 海量数据情景下使用虚拟树

模拟数据总共有100个子节点，每个子节点的深度为4，每一层级最多有20条数据，展示的节点最多总共为 100 * 20 * 20 * 20 * 20 = 16000000

```jsx
import React from 'react';
import { Tree } from '@weapp/ui';

function constructTree(maxDeepness: number, maxNumberOfChildren: number, minNumOfNodes: number, deepness = 1): any {
  return new Array(minNumOfNodes).fill(deepness).map((si, i) => {
    const numberOfChildren = deepness === maxDeepness ? 0 : Math.round(Math.random() * maxNumberOfChildren);
    return {
      id: `Deepness:${deepness}-Node:${i + 1}-ChildrenNum:${numberOfChildren}-${Math.random()}`,
      content: `Deepness:${deepness}-Node:${i + 1}`,
      children: numberOfChildren ? constructTree(maxDeepness, maxNumberOfChildren, numberOfChildren, deepness + 1) : [],
    };
  });
};
const Nodes = constructTree(4, 20, 100)
export default class BasicDemo extends React.Component {
  state = {
    selectedKeys: []
  }
  render() {
    return (
      <div style={{width: '300px'}}>
        <Tree
          data={Nodes}
          virtualMode
          virtualContainerHeight={400}
        />
      </div>
    );
  }
}
```

## 后端接口公共组件 API：点击此处

## API

### 参数说明如下：

| 参数 | 说明 | 类型 | 是否必填 | 默认值 |
| --- | --- | --- | --- | --- |
| autoExpandOnSelect | 自动展开选中 | boolean | 否 | true |
| caseSensitive | 搜索是否区分大小写 | boolean | 否 | true |
| checkedKeys | 受控选中节点 | TreeRootIdArray | 否 | - |
| checkable | 是否为 checkbox 模式 | boolean | 否 | false |
| checkPosition | 选择框位置 | 'default' \| 'right' | 否 | default |
| className | 样式名 | string | 否 | - |
| currentLoadNodeId | 当前所选中的需要加载的节点，配合loading使用 | string \| number | 否 | - |
| data | 数据 | ITreeData[] | 是 | - |
| dataType | 数据类型 | 'tree' \| 'plain' | 否 | 'tree' |
| defaultExpandedKeys | 默认展开节点 | TreeRootIdArray | 否 | - |
| defaultExpandedLeavel | 默认展开层级 | number | 否 | - |
| defaultExpandedAutoByChecked | 默认展开已选数据 | boolean | 否 | - |
| disabledCheckedKeys | 默认禁用选中节点 | TreeRootIdArray | 否 | - |
| expandedKeys | 受控展开节点 | TreeRootIdArray | 否 | - |
| expandOnlyArrow | 是否仅通过箭头图标展开父节点 | boolean | 否 | false |
| foldable | 是否可折叠 | boolean | 否 | true |
| isAsync | 是否为异步树形 | boolean | 否 | - |
| isComplexDataByType | 是否为复杂数据类型 | boolean | 否 | - |
| isShowCheckboxByDisabled | 禁用节点时是否显示 checkbox | boolean | 否 | true |
| loading | 当可查询下一页数据时，数据加载中的开关 | boolean | 否 | false |
| needSearch | 是否开启搜索功能 | boolean | 否 | false |
| showLine | 是否展示连线(不支持虚拟树形) | boolean | 否 | false |
| animateHeightStyle | 子节点animateHeigh 样式复写 | CSSProperties | 否 |  |
| operations | 操作 | ITreeOperation[] | 否 | - |
| pageInfo | 是否拥有获取更多数据按钮 | ITreePageInfo[] | 否 | - |
| renderKey | 复杂数据类型 数据渲染 key 集合 | Partial | 否 | - |
| searchPlaceholder | 搜索文字提示 | string | 否 | '请输入内容' |
| searchLabel | 搜索文本 自定义 | string \| ReactNode | 否 | - |
| searchLabelTitle | 搜索文本 title 自定义 （当searchLabel传入为非 string类型使用） | string | 否 | - |
| selectedKeys | 受控点击节点 | TreeRootIdArray | 否 | [] |
| showMatchedNodeChildren | 搜索匹配到某个节点时，是否自动显示该节点的子节点（即使子节点本身不匹配搜索条件） | boolean | 否 | false |
| showAllBranch | 是否包含下级联动 | boolean | 否 | true |
| size | 大小 | 'medium' \| 'small' \| 'large' | 否 | medium |
| style | 行内样式 | CSSProperties | 否 | - |
| virtualMode | 是否开启虚拟树 | boolean | 否 | false |
| virtualContainerHeight | 当开启虚拟树的时候，虚拟树所在容器的高度，默认高度为200 | number \| string | 否 | 200 |
| appendDataFromNode | 根据某个节点在该子类中追加数据 | （id: number \| string, data: ITreeData[], appendData: ITreeData[]）=> ITreeData[] | 否 | - |
| customRenderNode | 节点名称自定义渲染 | (data: ITreeData, isExpanded?: boolean) => ReactNode; | 否 | - |
| customHeaderRender | 树列表头部自定义渲染 | () => ReactNode; | 否 | - |
| getCheckboxProps | 树形节点选择框配置 | (data: ITreeData) => TreeCheckboxProps | 否 | - |
| loadMore | 加载更多 | (data: ITreeData) => Promise | 否 | - |
| isRoot | 判断是否根节点 | (data: ITreeData) => boolean | 否 | - |
| onCheck | check 回调 | (selected: TreeRootIdArray, info: ITreeOncheckHelpInfo, config: { isChecked: boolean, change: ITreeData[] }) => void | 否 | - |
| onMouseEnter | 节点鼠标移入事件 回调 | (data: ITreeData, e: Event }) => void | 否 | - |
| onExpand | 展开回调 | (expandedKeys: TreeRootIdArray, data: ITreeData, config: { isExpanded: boolean }) => void | 否 | - |
| onLoadMore | 当存在pageInfo时，点击加载触发回调 | (id: string \| number, data: ITreeData, pageNum: string \| number) => void | 否 | - |
| onSearch | 搜索后回调事件 | (findRoot: ITreeData[], expandedKeys: TreeRootIdArray) => void | 否 | - |
| onSelect | select 回调 | (data: ITreeData, target: HTMLSpanElement) => void | 否 | - |
| renderIcon | 自定义渲染节点 icon | (root: ITreeData, isParent: boolean, isExpanded: boolean) => ReactNode | 否 | - |
| onRightMenuClick | 右键菜单点击事件回调 | (data: ITreeData, target: HTMLSpanElement) => void | 否 | - |
| treeNodeClassName | 节点class自定义配置 | string \| (data: ITreeData, rootInfoMap: ITreeRootInfoMap) => string | 否 | - |
| dragDropConfig | 节点拖拽自定义配置 | TreeDragDropConfig | 否 | - |
#### ITreeData 类型说明如下

```typescript
interface ITreeData {
  DEFAULT_RENDER_KEY?: string | number;
  expand?: boolean;
  isLeaf?: boolean;
  [key: string]: any;
}
```

#### TreeRootIdArray 类型说明如下

```typescript
type TreeRootIdArray = (string | number)[]
```

#### ITreeOperation 类型说明如下

```typescript
interface ITreeOperation {
  name: string;
  icon?: string | ReactNode;
  action: (data: ITreeData) => void;
  shouldRender?: (data: ITreeData) => boolean;
}
```

### TreeCheckboxProps 参数说明如下：

| 参数 | 说明 | 类型 | 是否必填 | 默认值 |
| --- | --- | --- | --- | --- |
| disabled | 是否禁用 | boolean | false | false |
| hidden | 是否显示 | boolean | false | true |
### TreeDragDropConfig 参数说明如下：

| 参数 | 说明 | 类型 | 是否必填 | 默认值 |
| --- | --- | --- | --- | --- |
| draggable | 是否允许拖拽 | boolean | false | false |
| canDrag | 自定义拖拽权限控制（对应节点新增class：drag-over-disabled） | (node: ITreeData) => boolean | false |  |
| canDrop | 自定义放置权限控制 | (dragNode: ITreeData, dropNode: ITreeData, dropPosition: 'before'  \| 'after' \| 'inside') => boolean | false |  |
| canDropTreeNode | 自定义放置权限控制(预置触发) | (node: ITreeData) => void | false |  |
| onDrop | 拖拽放置事件处理函数 | (params: TreeDropInfo) => void | false |  |
| customInsideOffset | 自定义横向偏移量（计算是否为 inside） | (node: ITreeData, position: { relativeX: number, relativeY: number, current: any }) => number |  |  |
#### canDrop 和 canDropTreeNode 的使用区别

- canDrop: 通过拖拽节点和放置节点之间的关系进行校验
- canDropTreeNode: 通过节点自身信息校验是否可以被拖拽放置进入(适合初始化时预先校验 对应节点会新增class：drop-over-disabled)

### TreeDropInfo 参数说明如下：

| 参数 | 说明 | 类型 | 是否必填 | 默认值 |
| --- | --- | --- | --- | --- |
| dragNode | 被拖拽的源节点 | ITreeData | false |  |
| dropNode | 放置目标节点 | ITreeData | false |  |
| position | 放置位置 | 'before'    \| 'after' \| 'inside' | false |  |
| newTreeData | 重新构建后的树数据 | ITreeData[] | false |  |
