Tree

When To Use#

Directory, organization, biological classification, country, and etc. Almost things of the world are tree structure. The Tree component is a way of representing the hierarchical relationship of these things,and you also can expand, collapse, select the treeNodes of it.

Examples

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);

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 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;
      });
      ar.splice(i, 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);
}

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);

    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
    multipleWhether allow to select multiple treeNodesbooleanfalse
    checkableWhether support add Checkbox before treeNodebooleanfalse
    defaultExpandAllWhether default to expand all treeNodesbooleanfalse
    defaultExpandedKeysSpecify keys of default expanded treeNodesstring[][]
    expandedKeys(controlled) Specifies keys of expanded treeNodesstring[][]
    autoExpandParentWhether to automatically expand a parent treeNodebooleantrue
    defaultCheckedKeysSpecifies keys of default checked treeNodesstring[][]
    checkedKeys(controlled) Specifies keys of checked treeNodes(PS: When specifies a key of treeNode which is a parent treeNode, all children treeNodes of its will be checked; And vice versa, when specifies a key of treeNode which is a child treeNode, its parent treeNode will also be checked. When checkable and checkStrictly is true, it'a object has checked and halfChecked property, and no matter child treeNode or parent treeNode is checked, they won't impact on eachother.string[] | {checked: string[], halfChecked: string[]}[]
    checkStrictlyCheck treeNode precisely, parent treeNode and children treeNodes are not associatedbooleanfalse
    defaultSelectedKeysSpecifies keys of default selected treeNodesstring[][]
    selectedKeys(controlled) Specifies keys of selected treeNodestring[]-
    onExpandDefines a function will be called when expand or collapse a treeNodefunction(expandedKeys, {expanded: bool, node})-
    onCheckDefines a function will be called when the onCheck event occursfunction(checkedKeys, e:{checked: bool, checkedNodes, node, event})-
    onSelectThe callback will be invoked when the user clicks a treeNodefunction(selectedKeys, e:{selected: bool, selectedNodes, node, event})-
    filterTreeNodeDefines a function to filter treeNodes(highlight),when return true, corresponding treeNode will be highlightfunction(node)-
    loadDataload data asynchronouslyfunction(node)-
    onRightClickThe call back will be invoked when the user right clicks a treeNodefunction({event, node})-
    draggableSpecifies whether this Tree is draggable(IE>8)booleanfalse
    onDragStartDefines a function will be called when the onDragStart event occursfunction({event, node})-
    onDragEnterDefines a function will be called when the onDragEnter event occursfunction({event, node, expandedKeys})-
    onDragOverDefines a function will be called when the onDragOver event occursfunction({event, node})-
    onDragLeaveDefines a function will be called when the onDragLeave event occursfunction({event, node})-
    onDragEndDefines a function will be called when the onDragEnd event occursfunction({event, node})-
    onDropDefines a function will be called when the onDrop event occursfunction({event, node, dragNode, dragNodesKeys})-
    showLineWhether show connecting linebooleanfalse
    showIconWhether show the icon before TreeNode title, which has no default style, you must set custom style for it if set to truebooleanfalse

    TreeNode props#

    PropertyDescriptionTypeDefault
    disabledwhether disabled the treeNodebooleanfalse
    disableCheckboxwhether disable the checkbox of treeNodebooleanfalse
    titletitlestring|ReactNode'---'
    keyit's used 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
    isLeafwhether it's leaf nodebooleanfalse

    Note#

    The number of treeNodes can be very large, but when enable checkable, it will spend more computing time, so we cache some calculations (e.g. this.treeNodesStates), to avoid double computing. But, this bring 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'}