Tree  树形控件

开发指南#

何时使用#

文件夹、组织架构、生物分类、国家地区等等,世间万物的大多数结构都是树形结构。使用树控件可以完整展现其中的层级关系,并具有展开收起选择等交互功能。

API#

import { Tree } from '@alifd/next';

const TreeNode = Tree.Node;

class Demo extends React.Component {
    onSelect(keys, info) {
        console.log('onSelect', keys, info);
    }

    onCheck(keys, info) {
        console.log('onCheck', keys, info);
    }

    onEditFinish(key, label, node) {
        console.log('onEditFinish', key, label, node);
    }

    onRightClick(info) {
        console.log('onRightClick', info);
    }

    render() {
        return (
            <Tree checkable editable
                defaultExpandedKeys={['2']}
                defaultCheckedKeys={['2', '4', '5']}
                onSelect={this.onSelect}
                onCheck={this.onCheck}
                onEditFinish={this.onEditFinish}
                onRightClick={this.onRightClick}>
                <TreeNode key="1" label="Component">
                    <TreeNode key="2" label="Form" selectable={false}>
                        <TreeNode key="4" label="Input" />
                        <TreeNode key="5" label="Select" disabled />
                    </TreeNode>
                    <TreeNode key="3" label="Display">
                        <TreeNode key="6" label="Table" />
                    </TreeNode>
                </TreeNode>
            </Tree>
        );
    }
}

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

最简单的用法,展示可展开,可选中,可勾选,可编辑,可右键,禁用,禁用勾选,默认展开,默认选中,默认勾选等功能。

code collapse
import { Checkbox, Tree } from '@alifd/next';

const data = [{
    key: '0-0',
    label: '0-0',
    children: [{
        key: '0-0-0',
        label: '0-0-0',
        children: [{
            key: '0-0-0-0',
            label: '0-0-0-0',
            children: [{
                key: '0-0-0-0-0',
                label: '0-0-0-0-0'
            }]
        }, {
            key: '0-0-0-1',
            label: '0-0-0-1'
        }]
    }, {
        key: '0-0-1',
        label: '0-0-1',
        children: [{
            key: '0-0-1-0',
            label: '0-0-1-0'
        }, {
            key: '0-0-1-1',
            label: '0-0-1-1'
        }]
    }]
}];

class Demo extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            checkedKeys: [],
            checkStrictly: false
        };
        this.handleCheck = this.handleCheck.bind(this);
        this.handleCheckStrictly = this.handleCheckStrictly.bind(this);
    }

    handleCheck(keys, info) {
        console.log(keys, info);

        this.setState({
            checkedKeys: keys
        });
    }

    handleCheckStrictly() {
        this.setState({
            checkStrictly: !this.state.checkStrictly,
            checkedKeys: []
        });
    }

    render() {
        const { checkedKeys, checkStrictly } = this.state;

        return (
            <div className="control-check-demo">
                <label className="strictly-check">
                    <Checkbox value={checkStrictly} onChange={this.handleCheckStrictly} />
                    <span className="strictly-text">Enable checkStrictly</span>
                </label>
                <Tree defaultExpandAll checkable checkStrictly={checkStrictly} checkedKeys={checkedKeys} onCheck={this.handleCheck} dataSource={data} />
            </div>
        );
    }
}

ReactDOM.render(<Demo />, mountNode);
.control-check-demo .strictly-check {
    display: block;
    margin-bottom: 10px;
}

.control-check-demo .strictly-text {
    display: inline-block;
    margin-left: 10px;
    vertical-align: middle;
    color: #666;
    font-size: 14px;
}

展示父子节点选中是否关联的用法。

code collapse
import { Checkbox, Tree } from '@alifd/next';

const data = [{
    key: '0-0',
    label: '0-0',
    children: [{
        key: '0-0-0',
        label: '0-0-0',
        children: [{
            key: '0-0-0-0',
            label: '0-0-0-0',
            children: [{
                key: '0-0-0-0-0',
                label: '0-0-0-0-0'
            }]
        }, {
            key: '0-0-0-1',
            label: '0-0-0-1'
        }]
    }, {
        key: '0-0-1',
        label: '0-0-1',
        children: [{
            key: '0-0-1-0',
            label: '0-0-1-0'
        }, {
            key: '0-0-1-1',
            label: '0-0-1-1'
        }]
    }]
}];

class Demo extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            selectedKeys: [],
            multiple: false
        };
        this.handleSelect = this.handleSelect.bind(this);
        this.handleCheck = this.handleCheck.bind(this);
    }

    handleSelect(keys, info) {
        console.log(keys, info);

        this.setState({
            selectedKeys: keys
        });
    }

    handleCheck() {
        this.setState({
            multiple: !this.state.multiple,
            selectedKeys: []
        });
    }

    render() {
        const { multiple, selectedKeys } = this.state;

        return (
            <div className="control-select-demo">
                <label className="multiple-check">
                    <Checkbox value={multiple} onChange={this.handleCheck} />
                    <span className="multiple-text">Enable multiple</span>
                </label>
                <Tree defaultExpandAll multiple={multiple} selectedKeys={selectedKeys} onSelect={this.handleSelect} dataSource={data} />
            </div>
        );
    }
}

ReactDOM.render(<Demo />, mountNode);
.control-select-demo .multiple-check {
    display: block;
    margin-bottom: 10px;
}

.control-select-demo .multiple-text {
    display: inline-block;
    margin-left: 10px;
    vertical-align: middle;
    color: #666;
    font-size: 14px;
}

展示单选与多选的用法。

code collapse
import { Tree } from '@alifd/next';

const data = [{
    label: 'Component',
    key: '1',
    children: [{
        label: 'Form',
        key: '2',
        selectable: false,
        children: [{
            label: 'Input',
            key: '4'
        }, {
            label: 'Select',
            key: '5',
            disabled: true
        }]
    }, {
        label: 'Display',
        key: '3',
        children: [{
            label: 'Table',
            key: '6'
        }]
    }]
}];

class Demo extends React.Component {
    onSelect(keys, info) {
        console.log('onSelect', keys, info);
    }

    onCheck(keys, info) {
        console.log('onCheck', keys, info);
    }

    onEditFinish(key, label, node) {
        console.log('onEditFinish', key, label, node);
    }

    onRightClick(info) {
        console.log('onRightClick', info);
    }

    render() {
        return (
            <Tree checkable editable
                defaultExpandedKeys={['2']}
                defaultCheckedKeys={['2', '4', '5']}
                onSelect={this.onSelect}
                onCheck={this.onCheck}
                onEditFinish={this.onEditFinish}
                onRightClick={this.onRightClick}
                dataSource={data} />
        );
    }
}

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

使用 dataSource 生成树结构,除设置 key, label, children 属性外,还可传入 TreeNode 的其他属性,包括 selectable,disabled,checkable, checkboxDisabled, isLeaf 等。

code collapse
import { Tree } from '@alifd/next';

const TreeNode = Tree.Node;

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({ label: 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 {
    constructor(props) {
        super(props);

        this.state = {
            gData
        };
    }

    onDrop(info) {
        const dragKey = info.dragNode.props.eventKey;
        const dropKey = info.node.props.eventKey;
        const dropPosition = info.dropPosition;
        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.dropPosition === 0) {
            loop(data, dropKey, (item) => {
                item.children = item.children || [];
                item.children.push(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() {
        const loop = data => data.map(item => {
            if (item.children) {
                return <TreeNode key={item.key} label={item.key}>{loop(item.children)}</TreeNode>;
            }
            return <TreeNode key={item.key} label={item.key} />;
        });

        return (
            <Tree draggable selectable={false} isLabelBlock defaultExpandedKeys={['0-0', '0-0-0', '0-0-0-0']} onDrop={this.onDrop.bind(this)}>
                {loop(this.state.gData)}
            </Tree>
        );
    }
}

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

将节点拖拽到其他节点内部或前后。

code collapse
import { Tree } from '@alifd/next';

class Demo extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            data: [
                { label: 'Expand to load', key: '0', },
                { label: 'Expand to load', key: '1' },
                { label: 'Leaf', key: '2', isLeaf: true }
            ]
        };

        this.onLoadData = this.onLoadData.bind(this);
    }

    onLoadData(node) {
        return new Promise(resolve => {
            if (node.props.children) {
                return resolve();
            }

            const { eventKey, pos } = node.props;
            const item = this.getItemByPos(pos);
            setTimeout(() => {
                item.children = [
                    { label: 'Child Tree', key: `${eventKey}-0` },
                    { label: 'Child Tree', key: `${eventKey}-1` }
                ];
                this.setState({
                    data: [...this.state.data]
                });
                resolve();
            }, 1000);
        });
    }

    getItemByPos(pos) {
        return pos.split('-').slice(1).reduce((ret, num) => ret.children[num], { children: this.state.data });
    }

    render() {
        return (
            <Tree dataSource={this.state.data} loadData={this.onLoadData} />
        );
    }
}

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

点击展开节点,动态加载数据。

code collapse
import { Tree } from '@alifd/next';

const TreeNode = Tree.Node;

ReactDOM.render(
    <Tree defaultExpandAll showLine>
        <TreeNode label="Trunk">
            <TreeNode label="Branch">
                <TreeNode label="Branch">
                    <TreeNode label="Leaf" />
                </TreeNode>
                <TreeNode label="Leaf" />
            </TreeNode>
            <TreeNode label="Branch">
                <TreeNode label="Leaf" />
                <TreeNode label="Leaf" />
            </TreeNode>
        </TreeNode>
    </Tree>, mountNode);

展示Tree组件带线的样式外观。

code collapse
import { Tree } from '@alifd/next';

const TreeNode = Tree.Node;

ReactDOM.render(
    <Tree defaultExpandAll isNodeBlock={{ defaultPaddingLeft: 50 }} defaultSelectedKeys={['1']} style={{ width: '300px' }}>
        <TreeNode label="Component" key="0">
            <TreeNode label="Form" key="1" disabled>
                <TreeNode label="Select" key="2">
                    <TreeNode label="TreeSelect" key="3" />
                </TreeNode>
                <TreeNode label="Input" key="4" />
            </TreeNode>
            <TreeNode label="Display" key="5">
                <TreeNode label="Card" key="6" />
                <TreeNode label="Table" key="7" />
            </TreeNode>
        </TreeNode>
    </Tree>, mountNode);

可以通过设置 isNodeBlock 为 true,来让树节点占满一行,isNodeBlock 也可传入一个对象,支持设置 defaultPaddingLeft(默认的左内边距)和 indent (缩进距离),另外注意 showLine 在开启 isNodeBlock 时失效。

code collapse
import { Search, Tree } from '@alifd/next';

const data = [{
    label: 'Component',
    key: '1',
    children: [{
        label: 'Form',
        key: '2',
        children: [{
            label: 'Input',
            key: '4'
        }, {
            label: 'Select',
            key: '5'
        }]
    }, {
        label: 'Display',
        key: '3',
        children: [{
            label: 'Table',
            key: '6'
        }]
    }]
}];

class Demo extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            expandedKeys: ['2'],
            autoExpandParent: true
        };
        this.matchedKeys = [];
        this.handleSearch = this.handleSearch.bind(this);
        this.handleExpand = this.handleExpand.bind(this);
    }

    handleSearch(value) {
        value = value.trim();
        if (!value) {
            this.matchedKeys = null;
            return;
        }

        const matchedKeys = [];
        const loop = data => data.forEach(item => {
            if (item.label.indexOf(value) > -1) {
                matchedKeys.push(item.key);
            }
            if (item.children && item.children.length) {
                loop(item.children);
            }
        });
        loop(data);

        this.setState({
            expandedKeys: [...matchedKeys],
            autoExpandParent: true
        });
        this.matchedKeys = matchedKeys;
    }

    handleExpand(keys) {
        this.setState({
            expandedKeys: keys,
            autoExpandParent: false
        });
    }

    render() {
        const { expandedKeys, autoExpandParent } = this.state;
        const filterTreeNode = node => {
            return this.matchedKeys && this.matchedKeys.indexOf(node.props.eventKey) > -1;
        };

        return (
            <div>
                <Search shape="simple" size="medium" style={{ width: '200px', marginBottom: '10px' }} onChange={this.handleSearch} />
                <Tree expandedKeys={expandedKeys} autoExpandParent={autoExpandParent} filterTreeNode={filterTreeNode} onExpand={this.handleExpand} dataSource={data} />
            </div>
        );
    }
}

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

展示可搜索的树。

code collapse
API

Tree#

参数 说明 类型 默认值
children 树节点 ReactNode -
dataSource 数据源,该属性优先级高于 children Array -
showLine 是否显示树的线 Boolean false
selectable 是否支持选中节点 Boolean true
selectedKeys (用于受控)当前选中节点 key 的数组 Array<String> -
defaultSelectedKeys (用于非受控)默认选中节点 key 的数组 Array<String> []
onSelect 选中或取消选中节点时触发的回调函数

签名:
Function(selectedKeys: Array, extra: Object) => void
参数:
selectedKeys: {Array} 选中节点key的数组
extra: {Object} 额外参数
extra.selectedNodes: {Array} 选中节点的数组
extra.node: {Object} 当前操作的节点
extra.selected: {Boolean} 当前操作是否是选中
Function () => {}
multiple 是否支持多选 Boolean false
checkable 是否支持勾选节点的复选框 Boolean false
checkedKeys (用于受控)当前勾选复选框节点 key 的数组或 {checked: Array, indeterminate: Array} 的对象 Array<String>/Object -
defaultCheckedKeys (用于非受控)默认勾选复选框节点 key 的数组 Array<String> []
checkStrictly 勾选节点复选框是否完全受控(父子节点选中状态不再关联) Boolean false
checkedStrategy 定义选中时回填的方式

可选值:
'all'(返回所有选中的节点)
'parent'(父子节点都选中时只返回父节点)
'child'(父子节点都选中时只返回子节点)
Enum 'all'
onCheck 勾选或取消勾选复选框时触发的回调函数

签名:
Function(checkedKeys: Array, extra: Object) => void
参数:
checkedKeys: {Array} 勾选复选框节点key的数组
extra: {Object} 额外参数
extra.checkedNodes: {Array} 勾选复选框节点的数组
extra.checkedNodesPositions: {Array} 包含有勾选复选框节点和其位置的对象的数组
extra.indeterminateKeys: {Array} 半选复选框节点 key 的数组
extra.node: {Object} 当前操作的节点
extra.checked: {Boolean} 当前操作是否是勾选
Function () => {}
expandedKeys (用于受控)当前展开的节点 key 的数组 Array<String> -
defaultExpandedKeys (用于非受控)默认展开的节点 key 的数组 Array<String> []
defaultExpandAll 是否默认展开所有节点 Boolean false
autoExpandParent 是否自动展开父节点 Boolean true
onExpand 展开或收起节点时触发的回调函数

签名:
Function(expandedKeys: Array, extra: Object) => void
参数:
expandedKeys: {Array} 展开的节点key的数组
extra: {Object} 额外参数
extra.node: {Object} 当前操作的节点
extra.expanded: {Boolean} 当前操作是否是展开
Function () => {}
editable 是否支持编辑节点内容 Boolean false
onEditFinish 编辑节点内容完成时触发的回调函数

签名:
Function(key: String, label: String, node: Object) => void
参数:
key: {String} 编辑节点的 key
label: {String} 编辑节点完成时节点的文本
node: {Object} 当前编辑的节点
Function () => {}
draggable 是否支持拖拽节点 Boolean false
onDragStart 开始拖拽节点时触发的回调函数

签名:
Function(info: Object) => void
参数:
info: {Object} 拖拽信息
info.event: {Object} 事件对象
info.node: {Object} 拖拽的节点
Function () => {}
onDragEnter 拖拽节点进入目标节点时触发的回调函数

签名:
Function(info: Object) => void
参数:
info: {Object} 拖拽信息
info.event: {Object} 事件对象
info.node: {Object} 目标节点
info.expandedKeys: {Array} 当前展开的节点key的数组
Function () => {}
onDragOver 拖拽节点在目标节点上移动的时候触发的回调函数

签名:
Function(info: Object) => void
参数:
info: {Object} 拖拽信息
info.event: {Object} 事件对象
info.node: {Object} 目标节点
Function () => {}
onDragLeave 拖拽节点离开目标节点时触发的回调函数

签名:
Function(info: Object) => void
参数:
info: {Object} 拖拽信息
info.event: {Object} 事件对象
info.node: {Object} 目标节点
Function () => {}
onDragEnd 拖拽节点拖拽结束时触发的回调函数

签名:
Function(info: Object) => void
参数:
info: {Object} 拖拽信息
info.event: {Object} 事件对象
info.node: {Object} 目标节点
Function () => {}
onDrop 拖拽节点放入目标节点内或前后触发的回调函数

签名:
Function(info: Object) => void
参数:
info: {Object} 拖拽信息
info.event: {Object} 事件对象
info.node: {Object} 目标节点
info.dragNode: {Object} 拖拽的节点
info.dragNodesKeys: {Array} 拖拽的节点和其子节点 key 的数组
info.dropPosition: {Number} 放置位置,-1代表当前节点前,0代表当前节点里,1代表当前节点后
Function () => {}
canDrop 节点是否可被作为拖拽的目标节点

签名:
Function(info: Object) => Boolean
参数:
info: {Object} 拖拽信息
info.node: {Object} 目标节点
info.dragNode: {Object} 拖拽的节点
info.dragNodesKeys: {Array} 拖拽的节点和其子节点 key 的数组
info.dropPosition: {Number} 放置位置,-1代表当前节点前,0代表当前节点里,1代表当前节点后
返回值:
{Boolean} 是否可以被当作目标节点
Function () => true
loadData 异步加载数据的函数

签名:
Function(node: Object) => void
参数:
node: {Object} 被点击展开的节点
Function -
filterTreeNode 按需筛选高亮节点

签名:
Function(node: Object) => Boolean
参数:
node: {Object} 待筛选的节点
返回值:
{Boolean} 是否被筛选中
Function -
onRightClick 右键点击节点时触发的回调函数

签名:
Function(info: Object) => void
参数:
info: {Object} 信息对象
info.event: {Object} 事件对象
info.node: {Object} 点击的节点
Function () => {}
isLabelBlock 设置节点是否占满剩余空间,一般用于统一在各节点右侧添加元素(借助 flex 实现,暂时只支持 ie10+) Boolean false
isNodeBlock 设置节点是否占满一行 Boolean/Object false
animation 是否开启展开收起动画 Boolean true

Tree.Node#

参数 说明 类型 默认值
children 树节点 ReactNode -
label 节点文本内容 ReactNode '---'
selectable 单独设置是否支持选中,覆盖 Tree 的 selectable Boolean -
checkable 单独设置是否出现复选框,覆盖 Tree 的 checkable Boolean -
editable 单独设置是否支持编辑,覆盖 Tree 的 editable Boolean -
draggable 单独设置是否支持拖拽,覆盖 Tree 的 draggable Boolean -
disabled 是否禁止节点响应 Boolean false
checkboxDisabled 是否禁止勾选节点复选框 Boolean false
isLeaf 是否是叶子节点,设置loadData时生效 Boolean false

可以为 Tree.Node 设置 key 值:<TreeNode key="102894" label="女装" />,一般为数据的 id 值,但必需保证其全局唯一性,key 的默认值为 Tree 内部计算出的位置字符串。