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
- parent 1-0
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-0-0
- 0-1
- 0-2
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-1
- 0-2
import { Tree, Input } from 'antd';
const TreeNode = Tree.TreeNode;
const Search = Input.Search;
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.key;
dataList.push({ key, title: key });
if (node.children) {
generateList(node.children, node.key);
}
}
};
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.value;
const expandedKeys = dataList.map((item) => {
if (item.key.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.key.indexOf(searchValue);
const beforeStr = item.key.substr(0, index);
const afterStr = item.key.substr(index + searchValue.length);
const title = index > -1 ? (
<span>
{beforeStr}
<span style={{ color: '#f50' }}>{searchValue}</span>
{afterStr}
</span>
) : <span>{item.key}</span>;
if (item.children) {
return (
<TreeNode key={item.key} title={title}>
{loop(item.children)}
</TreeNode>
);
}
return <TreeNode key={item.key} title={title} />;
});
return (
<div>
<Search style={{ marginBottom: 8 }} placeholder="Search" onChange={this.onChange} />
<Tree
onExpand={this.onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
>
{loop(gData)}
</Tree>
</div>
);
}
}
ReactDOM.render(<SearchTree />, mountNode);
- 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-0-0
- 0-1
- 0-2
import { Tree } from 'antd';
const TreeNode = Tree.TreeNode;
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',
}];
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) => {
console.log('onCheck', checkedKeys);
this.setState({ checkedKeys });
}
onSelect = (selectedKeys, info) => {
console.log('onSelect', info);
this.setState({ selectedKeys });
}
renderTreeNodes = (data) => {
return data.map((item) => {
if (item.children) {
return (
<TreeNode title={item.title} key={item.key} dataRef={item}>
{this.renderTreeNodes(item.children)}
</TreeNode>
);
}
return <TreeNode {...item} />;
});
}
render() {
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}
>
{this.renderTreeNodes(treeData)}
</Tree>
);
}
}
ReactDOM.render(<Demo />, mountNode);
- Expand to load
- Expand to load
- Tree Node
import { Tree } from 'antd';
const TreeNode = Tree.TreeNode;
class Demo 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) => {
return new Promise((resolve) => {
if (treeNode.props.children) {
resolve();
return;
}
setTimeout(() => {
treeNode.props.dataRef.children = [
{ title: 'Child Node', key: `${treeNode.props.eventKey}-0` },
{ title: 'Child Node', key: `${treeNode.props.eventKey}-1` },
];
this.setState({
treeData: [...this.state.treeData],
});
resolve();
}, 1000);
});
}
renderTreeNodes = (data) => {
return data.map((item) => {
if (item.children) {
return (
<TreeNode title={item.title} key={item.key} dataRef={item}>
{this.renderTreeNodes(item.children)}
</TreeNode>
);
}
return <TreeNode {...item} dataRef={item} />;
});
}
render() {
return (
<Tree loadData={this.onLoadData}>
{this.renderTreeNodes(this.state.treeData)}
</Tree>
);
}
}
ReactDOM.render(<Demo />, mountNode);
- parent 1
- parent 1-0
- leaf
- leaf
- leaf
- parent 1-1
- parent 1-2
- parent 1-0
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#
Property | Description | Type | Default |
---|---|---|---|
multiple | Allows selecting multiple treeNodes | boolean | false |
checkable | Adds a Checkbox before the treeNodes | boolean | false |
defaultExpandAll | Whether to expand all treeNodes by default | boolean | false |
defaultExpandedKeys | Specify the keys of the default expanded treeNodes | string[] | [] |
expandedKeys | (Controlled) Specifies the keys of the expanded treeNodes | string[] | [] |
autoExpandParent | Whether to automatically expand a parent treeNode | boolean | true |
defaultCheckedKeys | Specifies the keys of the default checked treeNodes | string[] | [] |
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 |
defaultSelectedKeys | Specifies the keys of the default selected treeNodes | string[] | [] |
selectedKeys | (Controlled) Specifies the keys of the selected treeNodes | string[] | - |
onExpand | Callback function for when a treeNode is expanded or collapsed | function(expandedKeys, {expanded: bool, node}) | - |
onCheck | Callback function for when the onCheck event occurs | function(checkedKeys, e:{checked: bool, checkedNodes, node, event}) | - |
onSelect | Callback function for when the user clicks a treeNode | function(selectedKeys, e:{selected: bool, selectedNodes, node, event}) | - |
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) | - |
onRightClick | Callback function for when the user right clicks a treeNode | function({event, node}) | - |
draggable | Specifies whether this Tree is draggable (IE > 8) | boolean | false |
onDragStart | Callback function for when the onDragStart event occurs | function({event, node}) | - |
onDragEnter | Callback function for when the onDragEnter event occurs | function({event, node, expandedKeys}) | - |
onDragOver | Callback function for when the onDragOver event occurs | function({event, node}) | - |
onDragLeave | Callback function for when the onDragLeave event occurs | function({event, node}) | - |
onDragEnd | Callback function for when the onDragEnd event occurs | function({event, node}) | - |
onDrop | Callback function for when the onDrop event occurs | function({event, node, dragNode, dragNodesKeys}) | - |
showLine | Shows a connecting line | boolean | false |
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 |
TreeNode props#
Property | Description | Type | Default |
---|---|---|---|
disabled | Disables the treeNode | boolean | false |
disableCheckbox | Disables the checkbox of the treeNode | boolean | false |
title | Title | string|ReactNode | '---' |
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 |
isLeaf | Determines if this is a leaf node(effective when loadData is specified) | boolean | false |
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'}