Tree 树形控件

如果项目中使用的是 0.x 版本的基础组件(@icedesign/base, @ali/ice, @alife/next),请在左侧导航顶部切换组件版本。

安装方法

  1. 在命令行中执行以下命令npm install @alifd/next@latest -S

开发指南

何时使用

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

API

Tree

参数说明类型默认值
children树节点ReactNode-
dataSource数据源,该属性优先级高于 childrenArray-
showLine是否显示树的线Booleanfalse
selectable是否支持选中节点Booleantrue
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} 当前操作是否是选中Functionfunc.noop
multiple是否支持多选Booleanfalse
checkable是否支持勾选节点的复选框Booleanfalse
checkedKeys(用于受控)当前勾选复选框节点 key 的数组或 {checked: Array, indeterminate: Array} 的对象Array<String>/Object-
defaultCheckedKeys(用于非受控)默认勾选复选框节点 key 的数组Array<String>[]
checkStrictly勾选节点复选框是否完全受控(父子节点选中状态不再关联)Booleanfalse
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} 当前操作是否是勾选Functionfunc.noop
expandedKeys(用于受控)当前展开的节点 key 的数组Array<String>-
defaultExpandedKeys(用于非受控)默认展开的节点 key 的数组Array<String>[]
defaultExpandAll是否默认展开所有节点Booleanfalse
autoExpandParent是否自动展开父节点Booleantrue
onExpand展开或收起节点时触发的回调函数签名:Function(expandedKeys: Array, extra: Object) => void参数:expandedKeys: {Array} 展开的节点key的数组extra: {Object} 额外参数extra.node: {Object} 当前操作的节点extra.expanded: {Boolean} 当前操作是否是展开Functionfunc.noop
editable是否支持编辑节点内容Booleanfalse
onEditFinish编辑节点内容完成时触发的回调函数签名:Function(key: String, label: String, node: Object) => void参数:key: {String} 编辑节点的 keylabel: {String} 编辑节点完成时节点的文本node: {Object} 当前编辑的节点Functionfunc.noop
draggable是否支持拖拽节点Booleanfalse
onDragStart开始拖拽节点时触发的回调函数签名:Function(info: Object) => void参数:info: {Object} 拖拽信息info.event: {Object} 事件对象info.node: {Object} 拖拽的节点Functionfunc.noop
onDragEnter拖拽节点进入目标节点时触发的回调函数签名:Function(info: Object) => void参数:info: {Object} 拖拽信息info.event: {Object} 事件对象info.node: {Object} 目标节点info.expandedKeys: {Array} 当前展开的节点key的数组Functionfunc.noop
onDragOver拖拽节点在目标节点上移动的时候触发的回调函数签名:Function(info: Object) => void参数:info: {Object} 拖拽信息info.event: {Object} 事件对象info.node: {Object} 目标节点Functionfunc.noop
onDragLeave拖拽节点离开目标节点时触发的回调函数签名:Function(info: Object) => void参数:info: {Object} 拖拽信息info.event: {Object} 事件对象info.node: {Object} 目标节点Functionfunc.noop
onDragEnd拖拽节点拖拽结束时触发的回调函数签名:Function(info: Object) => void参数:info: {Object} 拖拽信息info.event: {Object} 事件对象info.node: {Object} 目标节点Functionfunc.noop
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代表当前节点后Functionfunc.noop
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} 点击的节点Functionfunc.noop
isLabelBlock设置节点是否占满剩余空间,一般用于统一在各节点右侧添加元素(借助 flex 实现,暂时只支持 ie10+)Booleanfalse
isNodeBlock设置节点是否占满一行Boolean/Objectfalse
animation是否开启展开收起动画Booleantrue
focusedKey当前获得焦点的子菜单或菜单项 key 值String-

Tree.Node

参数说明类型默认值
children树节点ReactNode-
label节点文本内容ReactNode'—-'
selectable单独设置是否支持选中,覆盖 Tree 的 selectableBoolean-
checkable单独设置是否出现复选框,覆盖 Tree 的 checkableBoolean-
editable单独设置是否支持编辑,覆盖 Tree 的 editableBoolean-
draggable单独设置是否支持拖拽,覆盖 Tree 的 draggableBoolean-
disabled是否禁止节点响应Booleanfalse
checkboxDisabled是否禁止勾选节点复选框Booleanfalse
isLeaf是否是叶子节点,设置loadData时生效Booleanfalse
<!— api-extra-start —>可以为 Tree.Node 设置 key 值:<TreeNode key="102894" label="女装" />,一般为数据的 id 值,但必需保证其全局唯一性,key 的默认值为 Tree 内部计算出的位置字符串。<!— api-extra-end —>

代码示例

基本

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

Tree 树形控件 - 图1

查看源码在线预览

  1. import { Tree } from '@alifd/next';
  2. const TreeNode = Tree.Node;
  3. class Demo extends React.Component {
  4. onSelect(keys, info) {
  5. console.log('onSelect', keys, info);
  6. }
  7. onCheck(keys, info) {
  8. console.log('onCheck', keys, info);
  9. }
  10. onEditFinish(key, label, node) {
  11. console.log('onEditFinish', key, label, node);
  12. }
  13. onRightClick(info) {
  14. console.log('onRightClick', info);
  15. }
  16. render() {
  17. return (
  18. <Tree checkable editable
  19. defaultExpandedKeys={['2']}
  20. defaultCheckedKeys={['2', '4', '5']}
  21. onSelect={this.onSelect}
  22. onCheck={this.onCheck}
  23. onEditFinish={this.onEditFinish}
  24. onRightClick={this.onRightClick}>
  25. <TreeNode key="1" label="Component">
  26. <TreeNode key="2" label="Form" selectable={false}>
  27. <TreeNode key="4" label="Input" />
  28. <TreeNode key="5" label="Select" disabled />
  29. </TreeNode>
  30. <TreeNode key="3" label="Display">
  31. <TreeNode key="6" label="Table" />
  32. </TreeNode>
  33. </TreeNode>
  34. </Tree>
  35. );
  36. }
  37. }
  38. ReactDOM.render(<Demo />, mountNode);

使用数据直接生成

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

Tree 树形控件 - 图2

查看源码在线预览

  1. import { Tree } from '@alifd/next';
  2. const data = [{
  3. label: 'Component',
  4. key: '1',
  5. children: [{
  6. label: 'Form',
  7. key: '2',
  8. selectable: false,
  9. children: [{
  10. label: 'Input',
  11. key: '4'
  12. }, {
  13. label: 'Select',
  14. key: '5',
  15. disabled: true
  16. }]
  17. }, {
  18. label: 'Display',
  19. key: '3',
  20. children: [{
  21. label: 'Table',
  22. key: '6'
  23. }]
  24. }]
  25. }];
  26. class Demo extends React.Component {
  27. onSelect(keys, info) {
  28. console.log('onSelect', keys, info);
  29. }
  30. onCheck(keys, info) {
  31. console.log('onCheck', keys, info);
  32. }
  33. onEditFinish(key, label, node) {
  34. console.log('onEditFinish', key, label, node);
  35. }
  36. onRightClick(info) {
  37. console.log('onRightClick', info);
  38. }
  39. render() {
  40. return (
  41. <Tree checkable editable
  42. defaultExpandedKeys={['2']}
  43. defaultCheckedKeys={['2', '4', '5']}
  44. onSelect={this.onSelect}
  45. onCheck={this.onCheck}
  46. onEditFinish={this.onEditFinish}
  47. onRightClick={this.onRightClick}
  48. dataSource={data} />
  49. );
  50. }
  51. }
  52. ReactDOM.render(<Demo />, mountNode);

带线样式

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

Tree 树形控件 - 图3

查看源码在线预览

  1. import { Tree } from '@alifd/next';
  2. const TreeNode = Tree.Node;
  3. ReactDOM.render(
  4. <Tree defaultExpandAll showLine>
  5. <TreeNode label="Trunk">
  6. <TreeNode label="Branch">
  7. <TreeNode label="Branch">
  8. <TreeNode label="Leaf" />
  9. </TreeNode>
  10. <TreeNode label="Leaf" />
  11. </TreeNode>
  12. <TreeNode label="Branch">
  13. <TreeNode label="Leaf" />
  14. <TreeNode label="Leaf" />
  15. </TreeNode>
  16. </TreeNode>
  17. </Tree>, mountNode);

树节点占满一行

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

Tree 树形控件 - 图4

查看源码在线预览

  1. import { Tree } from '@alifd/next';
  2. const TreeNode = Tree.Node;
  3. ReactDOM.render(
  4. <Tree defaultExpandAll isNodeBlock={{ defaultPaddingLeft: 50 }} defaultSelectedKeys={['1']} style={{ width: '300px' }}>
  5. <TreeNode label="Component" key="0">
  6. <TreeNode label="Form" key="1" disabled>
  7. <TreeNode label="Select" key="2">
  8. <TreeNode label="TreeSelect" key="3" />
  9. </TreeNode>
  10. <TreeNode label="Input" key="4" />
  11. </TreeNode>
  12. <TreeNode label="Display" key="5">
  13. <TreeNode label="Card" key="6" />
  14. <TreeNode label="Table" key="7" />
  15. </TreeNode>
  16. </TreeNode>
  17. </Tree>, mountNode);

单选与多选

展示单选与多选的用法。

Tree 树形控件 - 图5

查看源码在线预览

  1. import { Checkbox, Tree } from '@alifd/next';
  2. const data = [{
  3. key: '0-0',
  4. label: '0-0',
  5. children: [{
  6. key: '0-0-0',
  7. label: '0-0-0',
  8. children: [{
  9. key: '0-0-0-0',
  10. label: '0-0-0-0',
  11. children: [{
  12. key: '0-0-0-0-0',
  13. label: '0-0-0-0-0'
  14. }]
  15. }, {
  16. key: '0-0-0-1',
  17. label: '0-0-0-1'
  18. }]
  19. }, {
  20. key: '0-0-1',
  21. label: '0-0-1',
  22. children: [{
  23. key: '0-0-1-0',
  24. label: '0-0-1-0'
  25. }, {
  26. key: '0-0-1-1',
  27. label: '0-0-1-1'
  28. }]
  29. }]
  30. }];
  31. class Demo extends React.Component {
  32. constructor(props) {
  33. super(props);
  34. this.state = {
  35. selectedKeys: [],
  36. multiple: false
  37. };
  38. this.handleSelect = this.handleSelect.bind(this);
  39. this.handleCheck = this.handleCheck.bind(this);
  40. }
  41. handleSelect(keys, info) {
  42. console.log(keys, info);
  43. this.setState({
  44. selectedKeys: keys
  45. });
  46. }
  47. handleCheck() {
  48. this.setState({
  49. multiple: !this.state.multiple,
  50. selectedKeys: []
  51. });
  52. }
  53. render() {
  54. const { multiple, selectedKeys } = this.state;
  55. return (
  56. <div className="control-select-demo">
  57. <label className="multiple-check">
  58. <Checkbox value={multiple} onChange={this.handleCheck} />
  59. <span className="multiple-text">Enable multiple</span>
  60. </label>
  61. <Tree defaultExpandAll multiple={multiple} selectedKeys={selectedKeys} onSelect={this.handleSelect} dataSource={data} />
  62. </div>
  63. );
  64. }
  65. }
  66. ReactDOM.render(<Demo />, mountNode);
  1. .control-select-demo .multiple-check {
  2. display: block;
  3. margin-bottom: 10px;
  4. }
  5. .control-select-demo .multiple-text {
  6. display: inline-block;
  7. margin-left: 10px;
  8. vertical-align: middle;
  9. color: #666;
  10. font-size: 14px;
  11. }

父子节点选中是否关联

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

Tree 树形控件 - 图6

查看源码在线预览

  1. import { Checkbox, Tree } from '@alifd/next';
  2. const data = [{
  3. key: '0-0',
  4. label: '0-0',
  5. children: [{
  6. key: '0-0-0',
  7. label: '0-0-0',
  8. children: [{
  9. key: '0-0-0-0',
  10. label: '0-0-0-0',
  11. children: [{
  12. key: '0-0-0-0-0',
  13. label: '0-0-0-0-0'
  14. }]
  15. }, {
  16. key: '0-0-0-1',
  17. label: '0-0-0-1'
  18. }]
  19. }, {
  20. key: '0-0-1',
  21. label: '0-0-1',
  22. children: [{
  23. key: '0-0-1-0',
  24. label: '0-0-1-0'
  25. }, {
  26. key: '0-0-1-1',
  27. label: '0-0-1-1'
  28. }]
  29. }]
  30. }];
  31. class Demo extends React.Component {
  32. constructor(props) {
  33. super(props);
  34. this.state = {
  35. checkedKeys: [],
  36. checkStrictly: false
  37. };
  38. this.handleCheck = this.handleCheck.bind(this);
  39. this.handleCheckStrictly = this.handleCheckStrictly.bind(this);
  40. }
  41. handleCheck(keys, info) {
  42. console.log(keys, info);
  43. this.setState({
  44. checkedKeys: keys
  45. });
  46. }
  47. handleCheckStrictly() {
  48. this.setState({
  49. checkStrictly: !this.state.checkStrictly,
  50. checkedKeys: []
  51. });
  52. }
  53. render() {
  54. const { checkedKeys, checkStrictly } = this.state;
  55. return (
  56. <div className="control-check-demo">
  57. <label className="strictly-check">
  58. <Checkbox value={checkStrictly} onChange={this.handleCheckStrictly} />
  59. <span className="strictly-text">Enable checkStrictly</span>
  60. </label>
  61. <Tree defaultExpandAll checkable checkStrictly={checkStrictly} checkedKeys={checkedKeys} onCheck={this.handleCheck} dataSource={data} />
  62. </div>
  63. );
  64. }
  65. }
  66. ReactDOM.render(<Demo />, mountNode);
  1. .control-check-demo .strictly-check {
  2. display: block;
  3. margin-bottom: 10px;
  4. }
  5. .control-check-demo .strictly-text {
  6. display: inline-block;
  7. margin-left: 10px;
  8. vertical-align: middle;
  9. color: #666;
  10. font-size: 14px;
  11. }

可搜索的树

展示可搜索的树。

Tree 树形控件 - 图7

查看源码在线预览

  1. import { Search, Tree } from '@alifd/next';
  2. const data = [{
  3. label: 'Component',
  4. key: '1',
  5. children: [{
  6. label: 'Form',
  7. key: '2',
  8. children: [{
  9. label: 'Input',
  10. key: '4'
  11. }, {
  12. label: 'Select',
  13. key: '5'
  14. }]
  15. }, {
  16. label: 'Display',
  17. key: '3',
  18. children: [{
  19. label: 'Table',
  20. key: '6'
  21. }]
  22. }]
  23. }];
  24. class Demo extends React.Component {
  25. constructor(props) {
  26. super(props);
  27. this.state = {
  28. expandedKeys: ['2'],
  29. autoExpandParent: true
  30. };
  31. this.matchedKeys = [];
  32. this.handleSearch = this.handleSearch.bind(this);
  33. this.handleExpand = this.handleExpand.bind(this);
  34. }
  35. handleSearch(value) {
  36. value = value.trim();
  37. if (!value) {
  38. this.matchedKeys = null;
  39. return;
  40. }
  41. const matchedKeys = [];
  42. const loop = data => data.forEach(item => {
  43. if (item.label.indexOf(value) > -1) {
  44. matchedKeys.push(item.key);
  45. }
  46. if (item.children && item.children.length) {
  47. loop(item.children);
  48. }
  49. });
  50. loop(data);
  51. this.setState({
  52. expandedKeys: [...matchedKeys],
  53. autoExpandParent: true
  54. });
  55. this.matchedKeys = matchedKeys;
  56. }
  57. handleExpand(keys) {
  58. this.setState({
  59. expandedKeys: keys,
  60. autoExpandParent: false
  61. });
  62. }
  63. render() {
  64. const { expandedKeys, autoExpandParent } = this.state;
  65. const filterTreeNode = node => {
  66. return this.matchedKeys && this.matchedKeys.indexOf(node.props.eventKey) > -1;
  67. };
  68. return (
  69. <div>
  70. <Search shape="simple" size="medium" style={{ width: '200px', marginBottom: '10px' }} onChange={this.handleSearch} />
  71. <Tree expandedKeys={expandedKeys} autoExpandParent={autoExpandParent} filterTreeNode={filterTreeNode} onExpand={this.handleExpand} dataSource={data} />
  72. </div>
  73. );
  74. }
  75. }
  76. ReactDOM.render(<Demo />, mountNode);

异步加载数据

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

Tree 树形控件 - 图8

查看源码在线预览

  1. import { Tree } from '@alifd/next';
  2. class Demo extends React.Component {
  3. constructor(props) {
  4. super(props);
  5. this.state = {
  6. data: [
  7. { label: 'Expand to load', key: '0' },
  8. { label: 'Expand to load', key: '1' },
  9. { label: 'Leaf', key: '2', isLeaf: true }
  10. ]
  11. };
  12. this.onLoadData = this.onLoadData.bind(this);
  13. }
  14. onLoadData(node) {
  15. return new Promise(resolve => {
  16. if (node.props.children) {
  17. return resolve();
  18. }
  19. const { eventKey, pos } = node.props;
  20. const item = this.getItemByPos(pos);
  21. setTimeout(() => {
  22. item.children = [
  23. { label: 'Child Tree', key: `${eventKey}-0` },
  24. { label: 'Child Tree', key: `${eventKey}-1` }
  25. ];
  26. this.setState({
  27. data: [...this.state.data]
  28. });
  29. resolve();
  30. }, 1000);
  31. });
  32. }
  33. getItemByPos(pos) {
  34. return pos.split('-').slice(1).reduce((ret, num) => ret.children[num], { children: this.state.data });
  35. }
  36. render() {
  37. return (
  38. <Tree dataSource={this.state.data} loadData={this.onLoadData} />
  39. );
  40. }
  41. }
  42. ReactDOM.render(<Demo />, mountNode);

拖动

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

Tree 树形控件 - 图9

查看源码在线预览

  1. import { Tree } from '@alifd/next';
  2. const TreeNode = Tree.Node;
  3. const x = 3;
  4. const y = 2;
  5. const z = 1;
  6. const gData = [];
  7. const generateData = (_level, _preKey, _tns) => {
  8. const preKey = _preKey || '0';
  9. const tns = _tns || gData;
  10. const children = [];
  11. for (let i = 0; i < x; i++) {
  12. const key = `${preKey}-${i}`;
  13. tns.push({ label: key, key });
  14. if (i < y) {
  15. children.push(key);
  16. }
  17. }
  18. if (_level < 0) {
  19. return tns;
  20. }
  21. const level = _level - 1;
  22. children.forEach((key, index) => {
  23. tns[index].children = [];
  24. return generateData(level, key, tns[index].children);
  25. });
  26. };
  27. generateData(z);
  28. class Demo extends React.Component {
  29. constructor(props) {
  30. super(props);
  31. this.state = {
  32. gData
  33. };
  34. }
  35. onDrop(info) {
  36. if (!info.dragNode) {
  37. return;
  38. }
  39. const dragKey = info.dragNode.props.eventKey;
  40. const dropKey = info.node.props.eventKey;
  41. const dropPosition = info.dropPosition;
  42. const loop = (data, key, callback) => {
  43. data.forEach((item, index, arr) => {
  44. if (item.key === key) {
  45. return callback(item, index, arr);
  46. }
  47. if (item.children) {
  48. return loop(item.children, key, callback);
  49. }
  50. });
  51. };
  52. const data = [...this.state.gData];
  53. let dragObj;
  54. loop(data, dragKey, (item, index, arr) => {
  55. arr.splice(index, 1);
  56. dragObj = item;
  57. });
  58. if (info.dropPosition === 0) {
  59. loop(data, dropKey, (item) => {
  60. item.children = item.children || [];
  61. item.children.push(dragObj);
  62. });
  63. } else {
  64. let ar;
  65. let i;
  66. loop(data, dropKey, (item, index, arr) => {
  67. ar = arr;
  68. i = index;
  69. });
  70. if (dropPosition === -1) {
  71. ar.splice(i, 0, dragObj);
  72. } else {
  73. ar.splice(i + 1, 0, dragObj);
  74. }
  75. }
  76. this.setState({
  77. gData: data
  78. });
  79. }
  80. render() {
  81. const loop = data => data.map(item => {
  82. if (item.children) {
  83. return <TreeNode key={item.key} label={item.key}>{loop(item.children)}</TreeNode>;
  84. }
  85. return <TreeNode key={item.key} label={item.key} />;
  86. });
  87. return (
  88. <Tree draggable selectable={false} isLabelBlock defaultExpandedKeys={['0-0', '0-0-0', '0-0-0-0']} onDrop={this.onDrop.bind(this)}>
  89. {loop(this.state.gData)}
  90. </Tree>
  91. );
  92. }
  93. }
  94. ReactDOM.render(<Demo />, mountNode);

相关区块

Tree 树形控件 - 图10

暂无相关区块