Tree

When To Use#

Almost anything can be represented in a tree structure. Examples include directories, organization hierarchies, biological classifications, countries, etc. The Tree component is a way of representing the hierarchical relationship between these things. You can also expand, collapse, and select a treeNode within a Tree.

Examples

  • parent 1
    • parent 1-0
      • leaf
      • leaf
    • parent 1-1
      • sss

The most basic usage, tell you how to use checkable, selectable, disabled, defaultExpandKeys, and etc.

import { Tree } from 'antd';
const TreeNode = Tree.TreeNode;

class Demo extends React.Component {
  onSelect = (selectedKeys, info) => {
    console.log('selected', selectedKeys, info);
  }
  onCheck = (checkedKeys, info) => {
    console.log('onCheck', checkedKeys, info);
  }
  render() {
    return (
      <Tree
        checkable
        defaultExpandedKeys={['0-0-0', '0-0-1']}
        defaultSelectedKeys={['0-0-0', '0-0-1']}
        defaultCheckedKeys={['0-0-0', '0-0-1']}
        onSelect={this.onSelect}
        onCheck={this.onCheck}
      >
        <TreeNode title="parent 1" key="0-0">
          <TreeNode title="parent 1-0" key="0-0-0" disabled>
            <TreeNode title="leaf" key="0-0-0-0" disableCheckbox />
            <TreeNode title="leaf" key="0-0-0-1" />
          </TreeNode>
          <TreeNode title="parent 1-1" key="0-0-1">
            <TreeNode title={<span style={{ color: '#08c' }}>sss</span>} key="0-0-1-0" />
          </TreeNode>
        </TreeNode>
      </Tree>
    );
  }
}

ReactDOM.render(<Demo />, mountNode);
  • 0-0
    • 0-0-0
      • 0-0-0-0
      • 0-0-0-1
      • 0-0-0-2
    • 0-0-1
    • 0-0-2
  • 0-1
  • 0-2

Drag treeNode to insert after the other treeNode or insert into the other parent TreeNode.

import { Tree } from 'antd';
const TreeNode = Tree.TreeNode;

const x = 3;
const y = 2;
const z = 1;
const gData = [];

const generateData = (_level, _preKey, _tns) => {
  const preKey = _preKey || '0';
  const tns = _tns || gData;

  const children = [];
  for (let i = 0; i < x; i++) {
    const key = `${preKey}-${i}`;
    tns.push({ title: key, key });
    if (i < y) {
      children.push(key);
    }
  }
  if (_level < 0) {
    return tns;
  }
  const level = _level - 1;
  children.forEach((key, index) => {
    tns[index].children = [];
    return generateData(level, key, tns[index].children);
  });
};
generateData(z);

class Demo extends React.Component {
  state = {
    gData,
    expandedKeys: ['0-0', '0-0-0', '0-0-0-0'],
  }
  onDragEnter = (info) => {
    console.log(info);
    // expandedKeys 需要受控时设置
    // this.setState({
    //   expandedKeys: info.expandedKeys,
    // });
  }
  onDrop = (info) => {
    console.log(info);
    const dropKey = info.node.props.eventKey;
    const dragKey = info.dragNode.props.eventKey;
    const dropPos = info.node.props.pos.split('-');
    const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
    // const dragNodesKeys = info.dragNodesKeys;
    const loop = (data, key, callback) => {
      data.forEach((item, index, arr) => {
        if (item.key === key) {
          return callback(item, index, arr);
        }
        if (item.children) {
          return loop(item.children, key, callback);
        }
      });
    };
    const data = [...this.state.gData];
    let dragObj;
    loop(data, dragKey, (item, index, arr) => {
      arr.splice(index, 1);
      dragObj = item;
    });
    if (info.dropToGap) {
      let ar;
      let i;
      loop(data, dropKey, (item, index, arr) => {
        ar = arr;
        i = index;
      });
      if (dropPosition === -1) {
        ar.splice(i, 0, dragObj);
      } else {
        ar.splice(i - 1, 0, dragObj);
      }
    } else {
      loop(data, dropKey, (item) => {
        item.children = item.children || [];
        // where to insert 示例添加到尾部,可以是随意位置
        item.children.push(dragObj);
      });
    }
    this.setState({
      gData: data,
    });
  }
  render() {
    const loop = data => data.map((item) => {
      if (item.children && item.children.length) {
        return <TreeNode key={item.key} title={item.key}>{loop(item.children)}</TreeNode>;
      }
      return <TreeNode key={item.key} title={item.key} />;
    });
    return (
      <Tree
        className="draggable-tree"
        defaultExpandedKeys={this.state.expandedKeys}
        draggable
        onDragEnter={this.onDragEnter}
        onDrop={this.onDrop}
      >
        {loop(this.state.gData)}
      </Tree>
    );
  }
}

ReactDOM.render(<Demo />, mountNode);
/* You can add the following CSS to your project to make draggable area bigger */
#components-tree-demo-draggable .draggable-tree .ant-tree-node-content-wrapper {
  width: calc(100% - 18px);
}
  • 0-0
    • 0-0-0
      • 0-0-0-0
      • 0-0-0-1
      • 0-0-0-2
    • 0-0-1
      • 0-0-1-0
      • 0-0-1-1
      • 0-0-1-2
    • 0-0-2
  • 0-1
  • 0-2

basic controlled example

import { Tree } from 'antd';
const TreeNode = Tree.TreeNode;

const x = 3;
const y = 2;
const z = 1;
const gData = [];

const generateData = (_level, _preKey, _tns) => {
  const preKey = _preKey || '0';
  const tns = _tns || gData;

  const children = [];
  for (let i = 0; i < x; i++) {
    const key = `${preKey}-${i}`;
    tns.push({ title: key, key });
    if (i < y) {
      children.push(key);
    }
  }
  if (_level < 0) {
    return tns;
  }
  const level = _level - 1;
  children.forEach((key, index) => {
    tns[index].children = [];
    return generateData(level, key, tns[index].children);
  });
};
generateData(z);

class Demo extends React.Component {
  state = {
    expandedKeys: ['0-0-0', '0-0-1'],
    autoExpandParent: true,
    checkedKeys: ['0-0-0'],
    selectedKeys: [],
  }
  onExpand = (expandedKeys) => {
    console.log('onExpand', arguments);
    // if not set autoExpandParent to false, if children expanded, parent can not collapse.
    // or, you can remove all expanded children keys.
    this.setState({
      expandedKeys,
      autoExpandParent: false,
    });
  }
  onCheck = (checkedKeys) => {
    this.setState({
      checkedKeys,
      selectedKeys: ['0-3', '0-4'],
    });
  }
  onSelect = (selectedKeys, info) => {
    console.log('onSelect', info);
    this.setState({ selectedKeys });
  }
  render() {
    const loop = data => data.map((item) => {
      if (item.children) {
        return (
          <TreeNode key={item.key} title={item.key} disableCheckbox={item.key === '0-0-0'}>
            {loop(item.children)}
          </TreeNode>
        );
      }
      return <TreeNode key={item.key} title={item.key} />;
    });
    return (
      <Tree
        checkable
        onExpand={this.onExpand}
        expandedKeys={this.state.expandedKeys}
        autoExpandParent={this.state.autoExpandParent}
        onCheck={this.onCheck}
        checkedKeys={this.state.checkedKeys}
        onSelect={this.onSelect}
        selectedKeys={this.state.selectedKeys}
      >
        {loop(gData)}
      </Tree>
    );
  }
}

ReactDOM.render(<Demo />, mountNode);

    To load data asynchronously when click to expand a treeNode.

    import { Tree } from 'antd';
    const TreeNode = Tree.TreeNode;
    
    function generateTreeNodes(treeNode) {
      const arr = [];
      const key = treeNode.props.eventKey;
      for (let i = 0; i < 3; i++) {
        arr.push({ name: `leaf ${key}-${i}`, key: `${key}-${i}` });
      }
      return arr;
    }
    
    function setLeaf(treeData, curKey, level) {
      const loopLeaf = (data, lev) => {
        const l = lev - 1;
        data.forEach((item) => {
          if ((item.key.length > curKey.length) ? item.key.indexOf(curKey) !== 0 :
            curKey.indexOf(item.key) !== 0) {
            return;
          }
          if (item.children) {
            loopLeaf(item.children, l);
          } else if (l < 1) {
            item.isLeaf = true;
          }
        });
      };
      loopLeaf(treeData, level + 1);
    }
    
    function getNewTreeData(treeData, curKey, child, level) {
      const loop = (data) => {
        if (level < 1 || curKey.length - 3 > level * 2) return;
        data.forEach((item) => {
          if (curKey.indexOf(item.key) === 0) {
            if (item.children) {
              loop(item.children);
            } else {
              item.children = child;
            }
          }
        });
      };
      loop(treeData);
      setLeaf(treeData, curKey, level);
    }
    
    class Demo extends React.Component {
      state = {
        treeData: [],
      }
      componentDidMount() {
        setTimeout(() => {
          this.setState({
            treeData: [
              { name: 'pNode 01', key: '0-0' },
              { name: 'pNode 02', key: '0-1' },
              { name: 'pNode 03', key: '0-2', isLeaf: true },
            ],
          });
        }, 100);
      }
      onSelect = (info) => {
        console.log('selected', info);
      }
      onLoadData = (treeNode) => {
        return new Promise((resolve) => {
          setTimeout(() => {
            const treeData = [...this.state.treeData];
            getNewTreeData(treeData, treeNode.props.eventKey, generateTreeNodes(treeNode), 2);
            this.setState({ treeData });
            resolve();
          }, 1000);
        });
      }
      render() {
        const loop = data => data.map((item) => {
          if (item.children) {
            return <TreeNode title={item.name} key={item.key}>{loop(item.children)}</TreeNode>;
          }
          return <TreeNode title={item.name} key={item.key} isLeaf={item.isLeaf} disabled={item.key === '0-0-0'} />;
        });
        const treeNodes = loop(this.state.treeData);
        return (
          <Tree onSelect={this.onSelect} loadData={this.onLoadData}>
            {treeNodes}
          </Tree>
        );
      }
    }
    
    ReactDOM.render(<Demo />, mountNode);
    • parent 1
      • parent 1-0
        • leaf
        • leaf
        • leaf
      • parent 1-1
      • parent 1-2

    Tree With Line

    import { Tree } from 'antd';
    const TreeNode = Tree.TreeNode;
    
    class Demo extends React.Component {
      onSelect = (selectedKeys, info) => {
        console.log('selected', selectedKeys, info);
      }
      render() {
        return (
          <Tree
            showLine
            defaultExpandedKeys={['0-0-0']}
            onSelect={this.onSelect}
          >
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="parent 1-0" key="0-0-0">
                <TreeNode title="leaf" key="0-0-0-0" />
                <TreeNode title="leaf" key="0-0-0-1" />
                <TreeNode title="leaf" key="0-0-0-2" />
              </TreeNode>
              <TreeNode title="parent 1-1" key="0-0-1">
                <TreeNode title="leaf" key="0-0-1-0" />
              </TreeNode>
              <TreeNode title="parent 1-2" key="0-0-2">
                <TreeNode title="leaf" key="0-0-2-0" />
                <TreeNode title="leaf" key="0-0-2-1" />
              </TreeNode>
            </TreeNode>
          </Tree>
        );
      }
    }
    
    ReactDOM.render(<Demo />, mountNode);

    API#

    Tree props#

    PropertyDescriptionTypeDefault
    multipleAllows selecting multiple treeNodesbooleanfalse
    checkableAdds a Checkbox before the treeNodesbooleanfalse
    defaultExpandAllWhether to expand all treeNodes by defaultbooleanfalse
    defaultExpandedKeysSpecify the keys of the default expanded treeNodesstring[][]
    expandedKeys(Controlled) Specifies the keys of the expanded treeNodesstring[][]
    autoExpandParentWhether to automatically expand a parent treeNodebooleantrue
    defaultCheckedKeysSpecifies the keys of the default checked treeNodesstring[][]
    checkedKeys(Controlled) Specifies the keys of the checked treeNodes (PS: When this specifies the key of a treeNode which is also a parent treeNode, all the children treeNodes of will be checked; and vice versa, when it specifies the key of a treeNode which is a child treeNode, its parent treeNode will also be checked. When checkable and checkStrictly is true, its object has checked and halfChecked property. Regardless of whether the child or parent treeNode is checked, they won't impact each other.string[] | {checked: string[], halfChecked: string[]}[]
    checkStrictlyCheck treeNode precisely; parent treeNode and children treeNodes are not associatedbooleanfalse
    defaultSelectedKeysSpecifies the keys of the default selected treeNodesstring[][]
    selectedKeys(Controlled) Specifies the keys of the selected treeNodesstring[]-
    onExpandCallback function for when a treeNode is expanded or collapsedfunction(expandedKeys, {expanded: bool, node})-
    onCheckCallback function for when the onCheck event occursfunction(checkedKeys, e:{checked: bool, checkedNodes, node, event})-
    onSelectCallback function for when the user clicks a treeNodefunction(selectedKeys, e:{selected: bool, selectedNodes, node, event})-
    filterTreeNodeDefines a function to filter (highlight) treeNodes. When the function returns true, the corresponding treeNode will be highlightedfunction(node)-
    loadDataLoad data asynchronouslyfunction(node)-
    onRightClickCallback function for when the user right clicks a treeNodefunction({event, node})-
    draggableSpecifies whether this Tree is draggable (IE > 8)booleanfalse
    onDragStartCallback function for when the onDragStart event occursfunction({event, node})-
    onDragEnterCallback function for when the onDragEnter event occursfunction({event, node, expandedKeys})-
    onDragOverCallback function for when the onDragOver event occursfunction({event, node})-
    onDragLeaveCallback function for when the onDragLeave event occursfunction({event, node})-
    onDragEndCallback function for when the onDragEnd event occursfunction({event, node})-
    onDropCallback function for when the onDrop event occursfunction({event, node, dragNode, dragNodesKeys})-
    showLineShows a connecting linebooleanfalse
    showIconShows the icon before a TreeNode's title. There is no default style; you must set a custom style for it if set to truebooleanfalse

    TreeNode props#

    PropertyDescriptionTypeDefault
    disabledDisables the treeNodebooleanfalse
    disableCheckboxDisables the checkbox of the treeNodebooleanfalse
    titleTitlestring|ReactNode'---'
    keyUsed with (default)ExpandedKeys / (default)CheckedKeys / (default)SelectedKeys. P.S.: It must be unique in all of treeNodes of the tree!stringinternal calculated position of treeNode
    isLeafDetermines if this is a leaf nodebooleanfalse

    Note#

    The number of treeNodes can be very large, but when checkable=true, it will increase the compute time. So, we cache some calculations (e.g. this.treeNodesStates) to avoid double computing. But, this brings some restrictions. When you load treeNodes asynchronously, you should render tree like this:

    {this.state.treeData.length
      ? <Tree>{this.state.treeData.map(data => <TreeNode />)}</Tree>
      : 'loading tree'}