Tree
A hierarchical list structure component.
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
import { Tree } from 'antd';
const treeData = [
{
title: 'parent 1',
key: '0-0',
children: [
{
title: 'parent 1-0',
key: '0-0-0',
disabled: true,
children: [
{
title: 'leaf',
key: '0-0-0-0',
disableCheckbox: true,
},
{
title: 'leaf',
key: '0-0-0-1',
},
],
},
{
title: 'parent 1-1',
key: '0-0-1',
children: [{ title: <span style={{ color: '#1890ff' }}>sss</span>, key: '0-0-1-0' }],
},
],
},
];
const Demo = () => {
const onSelect = (selectedKeys, info) => {
console.log('selected', selectedKeys, info);
};
const onCheck = (checkedKeys, info) => {
console.log('onCheck', checkedKeys, info);
};
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={onSelect}
onCheck={onCheck}
treeData={treeData}
/>
);
};
ReactDOM.render(<Demo />, mountNode);
import { Tree } from 'antd';
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 loop = (data, key, callback) => {
for (let i = 0; i < data.length; i++) {
if (data[i].key === key) {
return callback(data[i], i, data);
}
if (data[i].children) {
loop(data[i].children, key, callback);
}
}
};
const data = [...this.state.gData];
// Find dragObject
let dragObj;
loop(data, dragKey, (item, index, arr) => {
arr.splice(index, 1);
dragObj = item;
});
if (!info.dropToGap) {
// Drop on the content
loop(data, dropKey, item => {
item.children = item.children || [];
// where to insert 示例添加到尾部,可以是随意位置
item.children.push(dragObj);
});
} else if (
(info.node.props.children || []).length > 0 && // Has children
info.node.props.expanded && // Is expanded
dropPosition === 1 // On the bottom gap
) {
loop(data, dropKey, item => {
item.children = item.children || [];
// where to insert 示例添加到头部,可以是随意位置
item.children.unshift(dragObj);
});
} else {
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);
}
}
this.setState({
gData: data,
});
};
render() {
return (
<Tree
className="draggable-tree"
defaultExpandedKeys={this.state.expandedKeys}
draggable
blockNode
onDragEnter={this.onDragEnter}
onDrop={this.onDrop}
treeData={this.state.gData}
/>
);
}
}
ReactDOM.render(<Demo />, mountNode);
import { Tree, Input } from 'antd';
const { Search } = Input;
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);
const dataList = [];
const generateList = data => {
for (let i = 0; i < data.length; i++) {
const node = data[i];
const { key } = node;
dataList.push({ key, title: key });
if (node.children) {
generateList(node.children);
}
}
};
generateList(gData);
const getParentKey = (key, tree) => {
let parentKey;
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node.children) {
if (node.children.some(item => item.key === key)) {
parentKey = node.key;
} else if (getParentKey(key, node.children)) {
parentKey = getParentKey(key, node.children);
}
}
}
return parentKey;
};
class SearchTree extends React.Component {
state = {
expandedKeys: [],
searchValue: '',
autoExpandParent: true,
};
onExpand = expandedKeys => {
this.setState({
expandedKeys,
autoExpandParent: false,
});
};
onChange = e => {
const { value } = e.target;
const expandedKeys = dataList
.map(item => {
if (item.title.indexOf(value) > -1) {
return getParentKey(item.key, gData);
}
return null;
})
.filter((item, i, self) => item && self.indexOf(item) === i);
this.setState({
expandedKeys,
searchValue: value,
autoExpandParent: true,
});
};
render() {
const { searchValue, expandedKeys, autoExpandParent } = this.state;
const loop = data =>
data.map(item => {
const index = item.title.indexOf(searchValue);
const beforeStr = item.title.substr(0, index);
const afterStr = item.title.substr(index + searchValue.length);
const title =
index > -1 ? (
<span>
{beforeStr}
<span className="site-tree-search-value">{searchValue}</span>
{afterStr}
</span>
) : (
<span>{item.title}</span>
);
if (item.children) {
return { title, key: item.key, children: loop(item.children) };
}
return {
title,
key: item.key,
};
});
return (
<div>
<Search style={{ marginBottom: 8 }} placeholder="Search" onChange={this.onChange} />
<Tree
onExpand={this.onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
treeData={loop(gData)}
/>
</div>
);
}
}
ReactDOM.render(<SearchTree />, mountNode);
.site-tree-search-value {
color: #f50;
}
import { Tree } from 'antd';
import {
DownOutlined,
FrownOutlined,
SmileOutlined,
MehOutlined,
FrownFilled,
} from '@ant-design/icons';
const treeData = [
{
title: 'parent 1',
key: '0-0',
icon: <SmileOutlined />,
children: [
{
title: 'leaf',
key: '0-0-0',
icon: <MehOutlined />,
},
{
title: 'leaf',
key: '0-0-1',
icon: ({ selected }) => (selected ? <FrownFilled /> : <FrownOutlined />),
},
],
},
];
ReactDOM.render(
<Tree
showIcon
defaultExpandAll
defaultSelectedKeys={['0-0-0']}
switcherIcon={<DownOutlined />}
treeData={treeData}
/>,
mountNode,
);
import { Tree } from 'antd';
import { DownOutlined } from '@ant-design/icons';
class Demo extends React.Component {
onSelect = (selectedKeys, info) => {
console.log('selected', selectedKeys, info);
};
render() {
return (
<Tree
showLine
switcherIcon={<DownOutlined />}
defaultExpandedKeys={['0-0-0']}
onSelect={this.onSelect}
treeData={[
{
title: 'parent 1',
key: '0-0',
children: [
{
title: 'parent 1-0',
key: '0-0-0',
children: [
{
title: 'leaf',
key: '0-0-0-0',
},
{
title: 'leaf',
key: '0-0-0-1',
},
{
title: 'leaf',
key: '0-0-0-2',
},
],
},
{
title: 'parent 1-1',
key: '0-0-1',
children: [
{
title: 'leaf',
key: '0-0-1-0',
},
],
},
{
title: 'parent 1-2',
key: '0-0-2',
children: [
{
title: 'leaf',
key: '0-0-2-0',
},
{
title: 'leaf',
key: '0-0-2-1',
},
],
},
],
},
]}
/>
);
}
}
ReactDOM.render(<Demo />, mountNode);
import React, { useState } from 'react';
import { Tree } from 'antd';
const treeData = [
{
title: '0-0',
key: '0-0',
children: [
{
title: '0-0-0',
key: '0-0-0',
children: [
{ title: '0-0-0-0', key: '0-0-0-0' },
{ title: '0-0-0-1', key: '0-0-0-1' },
{ title: '0-0-0-2', key: '0-0-0-2' },
],
},
{
title: '0-0-1',
key: '0-0-1',
children: [
{ title: '0-0-1-0', key: '0-0-1-0' },
{ title: '0-0-1-1', key: '0-0-1-1' },
{ title: '0-0-1-2', key: '0-0-1-2' },
],
},
{
title: '0-0-2',
key: '0-0-2',
},
],
},
{
title: '0-1',
key: '0-1',
children: [
{ title: '0-1-0-0', key: '0-1-0-0' },
{ title: '0-1-0-1', key: '0-1-0-1' },
{ title: '0-1-0-2', key: '0-1-0-2' },
],
},
{
title: '0-2',
key: '0-2',
},
];
const Demo = () => {
const [expandedKeys, setExpandedKeys] = useState<string[]>(['0-0-0', '0-0-1']);
const [checkedKeys, setCheckedKeys] = useState<string[]>(['0-0-0']);
const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);
const onExpand = expandedKeys => {
console.log('onExpand', expandedKeys);
// if not set autoExpandParent to false, if children expanded, parent can not collapse.
// or, you can remove all expanded children keys.
setExpandedKeys(expandedKeys);
setAutoExpandParent(false);
};
const onCheck = checkedKeys => {
console.log('onCheck', checkedKeys);
setCheckedKeys(checkedKeys);
};
const onSelect = (selectedKeys, info) => {
console.log('onSelect', info);
setSelectedKeys(selectedKeys);
};
return (
<Tree
checkable
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
onCheck={onCheck}
checkedKeys={checkedKeys}
onSelect={onSelect}
selectedKeys={selectedKeys}
treeData={treeData}
/>
);
};
ReactDOM.render(<Demo />, mountNode);
import React, { useState } from 'react';
import { Tree } from 'antd';
interface DataNode {
title: string;
key: string;
isLeaf?: boolean;
children?: DataNode[];
}
const initTreeDate: DataNode[] = [
{ title: 'Expand to load', key: '0' },
{ title: 'Expand to load', key: '1' },
{ title: 'Tree Node', key: '2', isLeaf: true },
];
// It's just a simple demo. You can use tree map to optimize update perf.
function updateTreeData(list: DataNode[], key: React.Key, children: DataNode[]): DataNode[] {
return list.map(node => {
if (node.key === key) {
return {
...node,
children,
};
} else if (node.children) {
return {
...node,
children: updateTreeData(node.children, key, children),
};
}
return node;
});
}
const Demo: React.FC<{}> = () => {
const [treeData, setTreeData] = useState(initTreeDate);
function onLoadData({ key, children }) {
return new Promise(resolve => {
if (children) {
resolve();
return;
}
setTimeout(() => {
setTreeData(origin =>
updateTreeData(origin, key, [
{ title: 'Child Node', key: `${key}-0` },
{ title: 'Child Node', key: `${key}-1` },
]),
);
resolve();
}, 1000);
});
}
return <Tree loadData={onLoadData} treeData={treeData} />;
};
class Demo1 extends React.Component {
state = {
treeData: [
{ title: 'Expand to load', key: '0' },
{ title: 'Expand to load', key: '1' },
{ title: 'Tree Node', key: '2', isLeaf: true },
],
};
onLoadData = treeNode => {
const { treeData } = this.state;
return new Promise(resolve => {
const { props } = treeNode;
if (treeNode.children) {
resolve();
return;
}
setTimeout(() => {
treeNode.children = [
{ title: 'Child Node', key: `${treeNode.eventKey}-0` },
{ title: 'Child Node', key: `${treeNode.eventKey}-1` },
];
this.setState({
treeData: [...this.state.treeData],
});
resolve();
}, 1000);
});
};
render() {
return <Tree loadData={this.onLoadData} treeData={this.state.treeData} />;
}
}
ReactDOM.render(<Demo />, mountNode);
showIcon:
showLeafIcon:
import React, { useState } from 'react';
import { Tree, Switch } from 'antd';
import { CarryOutOutlined, FormOutlined } from '@ant-design/icons';
const treeData = [
{
title: 'parent 1',
key: '0-0',
icon: <CarryOutOutlined />,
children: [
{
title: 'parent 1-0',
key: '0-0-0',
icon: <CarryOutOutlined />,
children: [
{ title: 'leaf', key: '0-0-0-0', icon: <CarryOutOutlined /> },
{ title: 'leaf', key: '0-0-0-1', icon: <CarryOutOutlined /> },
{ title: 'leaf', key: '0-0-0-2', icon: <CarryOutOutlined /> },
],
},
{
title: 'parent 1-1',
key: '0-0-1',
icon: <CarryOutOutlined />,
children: [{ title: 'leaf', key: '0-0-1-0', icon: <CarryOutOutlined /> }],
},
{
title: 'parent 1-2',
key: '0-0-2',
icon: <CarryOutOutlined />,
children: [
{ title: 'leaf', key: '0-0-2-0', icon: <CarryOutOutlined /> },
{
title: 'leaf',
key: '0-0-2-1',
icon: <CarryOutOutlined />,
switcherIcon: <FormOutlined />,
},
],
},
],
},
{
title: 'parent 2',
key: '0-1',
icon: <CarryOutOutlined />,
children: [
{
title: 'parent 2-0',
key: '0-1-0',
icon: <CarryOutOutlined />,
children: [
{ title: 'leaf', key: '0-1-0-0', icon: <CarryOutOutlined /> },
{ title: 'leaf', key: '0-1-0-1', icon: <CarryOutOutlined /> },
],
},
],
},
];
const Demo: React.FC<{}> = () => {
const [showLine, setShowLine] = useState(true);
const [showIcon, setShowIcon] = useState(false);
const [showLeafIcon, setShowLeafIcon] = useState(true);
const onSelect = (selectedKeys, info) => {
console.log('selected', selectedKeys, info);
};
const onSetLeafIcon = checked => {
setShowLeafIcon(checked);
setShowLine({ showLeafIcon: checked });
};
const onSetShowLine = checked => {
if (checked) {
showLeafIcon ? setShowLine(checked) : setShowLine({ showLeafIcon });
} else {
setShowLine(checked);
}
};
return (
<div>
<div style={{ marginBottom: 16 }}>
showLine: <Switch checked={showLine} onChange={onSetShowLine} />
<br />
<br />
showIcon: <Switch checked={showIcon} onChange={setShowIcon} />
<br />
<br />
showLeafIcon: <Switch checked={showLeafIcon} onChange={onSetLeafIcon} />
</div>
<Tree
showLine={showLine}
showIcon={showIcon}
defaultExpandedKeys={['0-0-0']}
onSelect={onSelect}
treeData={treeData}
/>
</div>
);
};
ReactDOM.render(<Demo />, mountNode);
import { Tree } from 'antd';
const { DirectoryTree } = Tree;
const treeData = [
{
title: 'parent 0',
key: '0-0',
children: [
{ title: 'leaf 0-0', key: '0-0-0', isLeaf: true },
{ title: 'leaf 0-1', key: '0-0-1', isLeaf: true },
],
},
{
title: 'parent 1',
key: '0-1',
children: [
{ title: 'leaf 1-0', key: '0-1-0', isLeaf: true },
{ title: 'leaf 1-1', key: '0-1-1', isLeaf: true },
],
},
];
const Demo: React.FC<{}> = () => {
const onSelect = (keys, event) => {
console.log('Trigger Select', keys, event);
};
const onExpand = () => {
console.log('Trigger Expand');
};
return (
<DirectoryTree
multiple
defaultExpandAll
onSelect={onSelect}
onExpand={onExpand}
treeData={treeData}
/>
);
};
ReactDOM.render(<Demo />, mountNode);
import { Tree } from 'antd';
function dig(path = '0', level = 3) {
const list = [];
for (let i = 0; i < 10; i += 1) {
const key = `${path}-${i}`;
const treeNode = {
title: key,
key,
};
if (level > 0) {
treeNode.children = dig(key, level - 1);
}
list.push(treeNode);
}
return list;
}
const treeData = dig();
ReactDOM.render(<Tree treeData={treeData} height={233} defaultExpandAll />, mountNode);
API#
Tree props#
Property | Description | Type | Default | Version |
---|---|---|---|---|
autoExpandParent | Whether to automatically expand a parent treeNode | boolean | false | |
blockNode | Whether treeNode fill remaining horizontal space | boolean | false | |
checkable | Add a Checkbox before the treeNodes | boolean | false | |
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[]} | [] | |
checkStrictly | Check treeNode precisely; parent treeNode and children treeNodes are not associated | boolean | false | |
defaultCheckedKeys | Specifies the keys of the default checked treeNodes | string[] | [] | |
defaultExpandAll | Whether to expand all treeNodes by default | boolean | false | |
defaultExpandedKeys | Specify the keys of the default expanded treeNodes | string[] | [] | |
defaultExpandParent | If auto expand parent treeNodes when init | boolean | true | |
defaultSelectedKeys | Specifies the keys of the default selected treeNodes | string[] | [] | |
disabled | Whether disabled the tree | boolean | false | |
draggable | Specifies whether this Tree is draggable (IE > 8) | boolean | false | |
expandedKeys | (Controlled) Specifies the keys of the expanded treeNodes | string[] | [] | |
filterTreeNode | Defines a function to filter (highlight) treeNodes. When the function returns true , the corresponding treeNode will be highlighted | function(node) | - | |
loadData | Load data asynchronously | function(node) | - | |
loadedKeys | (Controlled) Set loaded tree nodes. Need work with loadData | string[] | [] | |
multiple | Allows selecting multiple treeNodes | boolean | false | |
selectable | Whether can be selected | boolean | true | |
selectedKeys | (Controlled) Specifies the keys of the selected treeNodes | string[] | - | |
showIcon | Shows the icon before a TreeNode's title. There is no default style; you must set a custom style for it if set to true | boolean | false | |
switcherIcon | Customize collapse/expand icon of tree node | ReactNode | - | |
showLine | Shows a connecting line | boolean | {showLeafIcon: boolean} | false | |
titleRender | Customize tree node title render | (nodeData) => ReactNode | - | 4.5.0 |
treeData | The treeNodes data Array, if set it then you need not to construct children TreeNode. (key should be unique across the whole array) | array<{ key, title, children, [disabled, selectable] }> | - | |
virtual | Disable virtual scroll when set to false | boolean | true | 4.1.0 |
onCheck | Callback function for when the onCheck event occurs | function(checkedKeys, e:{checked: bool, checkedNodes, node, event, halfCheckedKeys}) | - | |
onDragEnd | Callback function for when the onDragEnd event occurs | function({event, node}) | - | |
onDragEnter | Callback function for when the onDragEnter event occurs | function({event, node, expandedKeys}) | - | |
onDragLeave | Callback function for when the onDragLeave event occurs | function({event, node}) | - | |
onDragOver | Callback function for when the onDragOver event occurs | function({event, node}) | - | |
onDragStart | Callback function for when the onDragStart event occurs | function({event, node}) | - | |
onDrop | Callback function for when the onDrop event occurs | function({event, node, dragNode, dragNodesKeys}) | - | |
onExpand | Callback function for when a treeNode is expanded or collapsed | function(expandedKeys, {expanded: bool, node}) | - | |
onLoad | Callback function for when a treeNode is loaded | function(loadedKeys, {event, node}) | - | |
onRightClick | Callback function for when the user right clicks a treeNode | function({event, node}) | - | |
onSelect | Callback function for when the user clicks a treeNode | function(selectedKeys, e:{selected: bool, selectedNodes, node, event}) | - | |
icon | Customize treeNode icon | ReactNode | (props) => ReactNode | - |
TreeNode props#
Property | Description | Type | Default | |
---|---|---|---|---|
checkable | When Tree is checkable, set TreeNode display Checkbox or not | boolean | - | |
disableCheckbox | Disables the checkbox of the treeNode | boolean | false | |
disabled | Disables the treeNode | boolean | false | |
icon | Customize icon. When you pass component, whose render will receive full TreeNode props as component props | ReactNode | (props) => ReactNode | - | |
isLeaf | Determines if this is a leaf node(effective when loadData is specified) | boolean | false | |
key | Used with (default)ExpandedKeys / (default)CheckedKeys / (default)SelectedKeys. P.S.: It must be unique in all of treeNodes of the tree | string | (internal calculated position of treeNode) | |
selectable | Set whether the treeNode can be selected | boolean | true | |
title | Title | string | ReactNode | --- |
DirectoryTree props#
Property | Description | Type | Default |
---|---|---|---|
expandAction | Directory open logic, optional: false | click | doubleClick | string | boolean | click |
Note#
Before 3.4.0
: 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'
);
}
Tree Methods#
Name | Description |
---|---|
scrollTo({ key }) | Scroll to key item in virtual scroll |
FAQ#
How to hide file icon when use showLine?#
File icon realize by using switcherIcon. You can overwrite the style to hide it: https://codesandbox.io/s/883vo47xp8
Why defaultExpandedAll not working on ajax data?#
default
prefix prop only works when inited. So defaultExpandedAll
has already executed when ajax load data. You can control expandedKeys
or render Tree when data loaded to realize expanded all.