Tree 树形控件

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

安装方法

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

何时使用

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

API

树形控件

参数说明类型默认值
prefix样式类名的品牌前缀String'next-'
className自定义类名String-
style自定义内联样式Object-
children树节点ReactNode-
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: {ReactElement} 当前操作的节点extra.selected: {Boolean} 当前操作是否是选中extra.event: {String} 当前操作的类型,值为'select'Function() => {}
multiple是否支持多选Booleanfalse
checkable是否支持勾选节点的复选框Booleanfalse
checkedKeys(用于受控)当前勾选复选框节点key的数组或{checked: Array, halfChecked: Array}的对象Array<String>/Object-
defaultCheckedKeys(用于非受控)默认勾选复选框节点key的数组Array<String>[]
checkStrictly勾选节点复选框是否完全受控(父子节点选中状态不再关联)Booleanfalse
enableCheckedCache是否启用勾选节点复选框的缓存来提高性能,如果dataSource需要被动态更新,请将其设置为falseBooleantrue
onCheck勾选或取消勾选复选框时触发的回调函数签名:Function(checkedKeys: Array, extra: Object) => void参数:checkedKeys: {Array} 勾选复选框节点key的数组extra: {Object} 额外参数extra.checkedNodes: {Array} 勾选复选框节点的数组extra.checkedNodesPositions: {Array} 包含有勾选复选框节点和其位置的对象的数组extra.halfCheckedKeys: {Array} 半选复选框节点key的数组extra.node: {ReactElement} 当前操作的节点extra.checked: {Boolean} 当前操作是否是勾选extra.event: {String} 当前操作的类型,值为'check'Function() => {}
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: {ReactElement} 当前操作的节点extra.expanded: {Boolean} 当前操作是否是展开Function() => {}
editable是否支持编辑节点内容Booleanfalse
onEditFinish编辑节点内容完成时触发的回调函数签名:Function(key: String, label: String) => void参数:key: {String} 编辑节点的keylabel: {String} 编辑节点完成时节点的文本Function() => {}
draggable是否支持拖拽节点Booleanfalse
onDragStart开始拖拽节点时触发的回调函数签名:Function(info: Object) => void参数:info: {Object} 拖拽信息info.event: {Object} 事件对象info.node: {ReactElement} 拖拽的节点Function() => {}
onDragEnter拖拽节点进入目标节点时触发的回调函数签名:Function(info: Object) => void参数:info: {Object} 拖拽信息info.event: {Object} 事件对象info.node: {ReactElement} 目标节点info.expandedKeys: {Array} 当前展开的节点key的数组Function() => {}
onDragOver拖拽节点在目标节点上移动的时候触发的回调函数签名:Function(info: Object) => void参数:info: {Object} 拖拽信息info.event: {Object} 事件对象info.node: {ReactElement} 目标节点Function() => {}
onDragLeave拖拽节点离开目标节点时触发的回调函数签名:Function(info: Object) => void参数:info: {Object} 拖拽信息info.event: {Object} 事件对象info.node: {ReactElement} 目标节点Function() => {}
onDrop拖拽节点放入目标节点内或前后触发的回调函数签名:Function(info: Object) => void参数:info: {Object} 拖拽信息info.event: {Object} 事件对象info.node: {ReactElement} 目标节点info.dragNode: {ReactElement} 拖拽的节点info.dragNodesKeys: {Array} 拖拽的节点和其子节点key的数组info.dropPosition: {Number} 拖拽的节点在拖拽后被放置在当前层级的位置info.dropToGap: {Boolean} 是否被放置在目标节点的前后(没有被放置在目标节点内部)Function() => {}
canDrop节点是否可被作为拖拽的目标节点签名:Function(info: Object) => Boolean参数:info: {Object} 拖拽信息info.node: {ReactElement} 目标节点info.dragNode: {ReactElement} 拖拽的节点info.dragNodesKeys: {Array} 拖拽的节点和其子节点key的数组info.dropPosition: {Number} 拖拽的节点在拖拽后被放置在当前层级的位置info.dropToGap: {Boolean} 是否被放置在目标节点的前后(没有被放置在目标节点内部)返回值:{Boolean} 是否可以被当作目标节点Function() => true
loadData异步加载数据的函数签名:Function(node: ReactElement) => void参数:node: {ReactElement} 被点击展开的节点Function-
filterTreeNode按需筛选高亮节点签名:Function(node: ReactElement) => Boolean参数:node: {ReactElement} 待筛选的节点返回值:{Boolean} 是否被筛选中Function-
onRightClick右键点击节点时触发的回调函数签名:Function(event: Object, node: ReactElement) => void参数:event: {Object} 事件对象node: {ReactElement} 点击的节点Function-
isLabelBlock设置节点是否占满剩余空间,一般用于统一在各节点右侧添加元素(借助flex实现,暂时只支持ie10+)Booleanfalse
animation是否开启展开收起动画Booleantrue

Tree.Node

参数说明类型默认值
prefix样式类名的品牌前缀String-
className自定义类名String-
style自定义内联样式Object-
children树节点ReactNode-
label节点文本内容ReactNode'—-'
selectable单独设置是否支持选中,覆盖Tree的selectableBoolean-
editable单独设置是否支持编辑,覆盖Tree的editableBoolean-
draggable单独设置是否支持拖拽,覆盖Tree的draggableBoolean-
disabled是否禁止节点响应Booleanfalse
disableCheckbox是否禁止勾选节点复选框Booleanfalse
isLeaf是否是叶子节点,设置loadData时生效Booleanfalse

代码示例

基本

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

Tree 树形控件 - 图1

查看源码在线预览

  1. import { Tree } from "@icedesign/base";
  2. const { Node: TreeNode } = Tree;
  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(info) {
  11. console.log("onEditFinish", info);
  12. }
  13. onRightClick(info) {
  14. console.log("onRightClick", info);
  15. }
  16. render() {
  17. return (
  18. <Tree
  19. multiple
  20. checkable
  21. editable
  22. defaultExpandedKeys={["0-0-0", "0-0-1"]}
  23. defaultCheckedKeys={["0-0-0", "0-0-1"]}
  24. onSelect={this.onSelect}
  25. onCheck={this.onCheck}
  26. onEditFinish={this.onEditFinish}
  27. onRightClick={this.onRightClick}
  28. >
  29. <TreeNode label="parent 1" key="0-0">
  30. <TreeNode label="parent 1-0" key="0-0-0" disabled>
  31. <TreeNode label="leaf" key="0-0-0-0" disableCheckbox />
  32. <TreeNode label="leaf" key="0-0-0-1" />
  33. </TreeNode>
  34. <TreeNode label="parent 1-1" key="0-0-1">
  35. <TreeNode
  36. label={<span style={{ color: "#08c" }}>abc</span>}
  37. key="0-0-1-0"
  38. />
  39. </TreeNode>
  40. </TreeNode>
  41. </Tree>
  42. );
  43. }
  44. }
  45. ReactDOM.render(<Demo />, mountNode);

父子节点选中是否关联

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

Tree 树形控件 - 图2

查看源码在线预览

  1. import { Tree, Checkbox } from "@icedesign/base";
  2. const { Node: TreeNode } = Tree;
  3. const data = [
  4. {
  5. id: "0-0",
  6. children: [
  7. {
  8. id: "0-0-0",
  9. children: [
  10. {
  11. id: "0-0-0-0",
  12. children: [
  13. {
  14. id: "0-0-0-0-0"
  15. }
  16. ]
  17. },
  18. {
  19. id: "0-0-0-1"
  20. }
  21. ]
  22. },
  23. {
  24. id: "0-0-1",
  25. children: [
  26. {
  27. id: "0-0-1-0"
  28. },
  29. {
  30. id: "0-0-1-1"
  31. }
  32. ]
  33. }
  34. ]
  35. }
  36. ];
  37. class Demo extends React.Component {
  38. constructor(props) {
  39. super(props);
  40. this.state = {
  41. checkedKeys: [],
  42. checkStrictly: false
  43. };
  44. this.handleCheck = this.handleCheck.bind(this);
  45. this.handleCheckStrictly = this.handleCheckStrictly.bind(this);
  46. }
  47. handleCheck(keys, info) {
  48. console.log(keys, info);
  49. this.setState({
  50. checkedKeys: keys
  51. });
  52. }
  53. handleCheckStrictly() {
  54. this.setState({
  55. checkStrictly: !this.state.checkStrictly,
  56. checkedKeys: []
  57. });
  58. }
  59. render() {
  60. const loop = data =>
  61. data.map(item => {
  62. return (
  63. <TreeNode label={item.id} key={item.id}>
  64. {item.children && item.children.length ? loop(item.children) : null}
  65. </TreeNode>
  66. );
  67. });
  68. const { checkedKeys, checkStrictly } = this.state;
  69. return (
  70. <div className="control-check-demo">
  71. <label className="strictly-check">
  72. <Checkbox value={checkStrictly} onChange={this.handleCheckStrictly} />
  73. <span className="strictly-text">
  74. 开启严格受控,父子节点选中不再关联
  75. </span>
  76. </label>
  77. <Tree
  78. defaultExpandAll
  79. checkable
  80. checkStrictly={checkStrictly}
  81. checkedKeys={checkedKeys}
  82. onCheck={this.handleCheck}
  83. >
  84. {loop(data)}
  85. </Tree>
  86. </div>
  87. );
  88. }
  89. }
  90. 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 树形控件 - 图3

查看源码在线预览

  1. import { Tree, Checkbox } from "@icedesign/base";
  2. const { Node: TreeNode } = Tree;
  3. const data = [
  4. {
  5. id: "0-0",
  6. children: [
  7. {
  8. id: "0-0-0",
  9. children: [
  10. {
  11. id: "0-0-0-0",
  12. children: [
  13. {
  14. id: "0-0-0-0-0"
  15. }
  16. ]
  17. },
  18. {
  19. id: "0-0-0-1"
  20. }
  21. ]
  22. },
  23. {
  24. id: "0-0-1",
  25. children: [
  26. {
  27. id: "0-0-1-0"
  28. },
  29. {
  30. id: "0-0-1-1"
  31. }
  32. ]
  33. }
  34. ]
  35. }
  36. ];
  37. class Demo extends React.Component {
  38. constructor(props) {
  39. super(props);
  40. this.state = {
  41. selectedKeys: [],
  42. multiple: false
  43. };
  44. this.handleSelect = this.handleSelect.bind(this);
  45. this.handleCheck = this.handleCheck.bind(this);
  46. }
  47. handleSelect(keys, info) {
  48. console.log(keys, info);
  49. this.setState({
  50. selectedKeys: keys
  51. });
  52. }
  53. handleCheck() {
  54. this.setState({
  55. multiple: !this.state.multiple,
  56. selectedKeys: []
  57. });
  58. }
  59. render() {
  60. const loop = data =>
  61. data.map(item => {
  62. return (
  63. <TreeNode label={item.id} key={item.id}>
  64. {item.children && item.children.length ? loop(item.children) : null}
  65. </TreeNode>
  66. );
  67. });
  68. const { multiple, selectedKeys } = this.state;
  69. return (
  70. <div className="control-select-demo">
  71. <label className="multiple-check">
  72. <Checkbox value={multiple} onChange={this.handleCheck} />
  73. <span className="multiple-text">开启多选</span>
  74. </label>
  75. <Tree
  76. defaultExpandAll
  77. multiple={multiple}
  78. selectedKeys={selectedKeys}
  79. onSelect={this.handleSelect}
  80. >
  81. {loop(data)}
  82. </Tree>
  83. </div>
  84. );
  85. }
  86. }
  87. 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 树形控件 - 图4

查看源码在线预览

  1. import { Tree } from "@icedesign/base";
  2. const { Node: TreeNode } = Tree;
  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. expandedKeys: ["0-0", "0-0-0", "0-0-0-0"]
  34. };
  35. }
  36. // eslint-disable-next-line
  37. onDragEnter(info) {
  38. // expandedKeys 需要受控时设置
  39. // this.setState({
  40. // expandedKeys: info.expandedKeys,
  41. // });
  42. }
  43. onDrop(info) {
  44. const dragKey = info.dragNode.props.eventKey;
  45. const dropKey = info.node.props.eventKey;
  46. const dropPos = info.node.props.pos.split("-");
  47. // 通过info.node.props.pos计算得到的dropPos为-1则为拖动到节点之上,1则为拖动到节点之下
  48. const dropPosition =
  49. info.dropPosition - Number(dropPos[dropPos.length - 1]);
  50. const loop = (data, key, callback) => {
  51. data.forEach((item, index, arr) => {
  52. if (item.key === key) {
  53. return callback(item, index, arr);
  54. }
  55. if (item.children) {
  56. return loop(item.children, key, callback);
  57. }
  58. });
  59. };
  60. const data = [...this.state.gData];
  61. let dragObj;
  62. loop(data, dragKey, (item, index, arr) => {
  63. arr.splice(index, 1);
  64. dragObj = item;
  65. });
  66. // dropToGap为true 则拖动到节点内部
  67. if (info.dropToGap) {
  68. let ar;
  69. let i;
  70. loop(data, dropKey, (item, index, arr) => {
  71. ar = arr;
  72. i = index;
  73. });
  74. if (dropPosition === -1) {
  75. ar.splice(i, 0, dragObj);
  76. } else {
  77. ar.splice(i + 1, 0, dragObj);
  78. }
  79. } else {
  80. loop(data, dropKey, item => {
  81. item.children = item.children || [];
  82. // where to insert 示例添加到尾部,可以是随意位置
  83. item.children.push(dragObj);
  84. });
  85. }
  86. this.setState({
  87. gData: data
  88. });
  89. }
  90. render() {
  91. const loop = data =>
  92. data.map(item => {
  93. if (item.children) {
  94. return (
  95. <TreeNode key={item.key} label={item.key}>
  96. {loop(item.children)}
  97. </TreeNode>
  98. );
  99. }
  100. return <TreeNode key={item.key} label={item.key} />;
  101. });
  102. return (
  103. <Tree
  104. defaultExpandedKeys={this.state.expandedKeys}
  105. draggable
  106. selectable={false}
  107. onDragEnter={this.onDragEnter.bind(this)}
  108. onDrop={this.onDrop.bind(this)}
  109. >
  110. {loop(this.state.gData)}
  111. </Tree>
  112. );
  113. }
  114. }
  115. ReactDOM.render(<Demo />, mountNode);

异步加载数据

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

Tree 树形控件 - 图5

查看源码在线预览

  1. import { Tree } from "@icedesign/base";
  2. const { Node: TreeNode } = Tree;
  3. function generateTreeNodes(treeNode) {
  4. const arr = [];
  5. const key = treeNode.props.eventKey;
  6. for (let i = 0; i < 3; i++) {
  7. arr.push({ name: `leaf ${key}-${i}`, key: `${key}-${i}` });
  8. }
  9. return arr;
  10. }
  11. function setLeaf(treeData, curKey, level) {
  12. const loopLeaf = (data, lev) => {
  13. const l = lev - 1;
  14. data.forEach(item => {
  15. if (
  16. item.key.length > curKey.length
  17. ? item.key.indexOf(curKey) !== 0
  18. : curKey.indexOf(item.key) !== 0
  19. ) {
  20. return;
  21. }
  22. if (item.children) {
  23. loopLeaf(item.children, l);
  24. } else if (l < 1) {
  25. item.isLeaf = true;
  26. }
  27. });
  28. };
  29. loopLeaf(treeData, level + 1);
  30. }
  31. function getNewTreeData(treeData, curKey, child, level) {
  32. const loop = data => {
  33. if (level < 1 || curKey.length - 3 > level * 2) {
  34. return;
  35. }
  36. data.forEach(item => {
  37. if (curKey.indexOf(item.key) === 0) {
  38. if (item.children) {
  39. loop(item.children);
  40. } else {
  41. item.children = child;
  42. }
  43. }
  44. });
  45. };
  46. loop(treeData);
  47. setLeaf(treeData, curKey, level);
  48. }
  49. class Demo extends React.Component {
  50. constructor(props) {
  51. super(props);
  52. this.state = {
  53. treeData: []
  54. };
  55. }
  56. componentDidMount() {
  57. setTimeout(() => {
  58. this.setState({
  59. treeData: [
  60. { name: "pNode 01", key: "0-0" },
  61. { name: "pNode 02", key: "0-1" },
  62. { name: "pNode 03", key: "0-2", isLeaf: true }
  63. ]
  64. });
  65. }, 100);
  66. }
  67. onSelect(info) {
  68. console.log("selected", info);
  69. }
  70. onLoadData(treeNode) {
  71. return new Promise(resolve => {
  72. setTimeout(() => {
  73. const treeData = [...this.state.treeData];
  74. getNewTreeData(
  75. treeData,
  76. treeNode.props.eventKey,
  77. generateTreeNodes(treeNode),
  78. 2
  79. );
  80. this.setState({ treeData });
  81. resolve();
  82. }, 500);
  83. });
  84. }
  85. render() {
  86. const loop = data =>
  87. data.map(item => {
  88. if (item.children) {
  89. return (
  90. <TreeNode label={item.name} key={item.key}>
  91. {loop(item.children)}
  92. </TreeNode>
  93. );
  94. }
  95. return (
  96. <TreeNode
  97. label={item.name}
  98. key={item.key}
  99. isLeaf={item.isLeaf}
  100. disabled={item.key === "0-0-0"}
  101. />
  102. );
  103. });
  104. const treeNodes = loop(this.state.treeData);
  105. return (
  106. <Tree
  107. onSelect={this.onSelect.bind(this)}
  108. loadData={this.onLoadData.bind(this)}
  109. >
  110. {treeNodes}
  111. </Tree>
  112. );
  113. }
  114. }
  115. ReactDOM.render(<Demo />, mountNode);

带线样式

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

Tree 树形控件 - 图6

查看源码在线预览

  1. import { Tree } from "@icedesign/base";
  2. const { Node: TreeNode } = Tree;
  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>,
  18. mountNode
  19. );

可搜索的树

展示可搜索的树。

Tree 树形控件 - 图7

查看源码在线预览

  1. import { Tree, Search } from "@icedesign/base";
  2. const { Node: TreeNode } = Tree;
  3. const data = [
  4. {
  5. id: "0-0",
  6. children: [
  7. {
  8. id: "0-0-0",
  9. children: [
  10. {
  11. id: "0-0-0-0",
  12. children: [
  13. {
  14. id: "0-0-0-0-0"
  15. }
  16. ]
  17. },
  18. {
  19. id: "0-0-0-1"
  20. }
  21. ]
  22. },
  23. {
  24. id: "0-0-1",
  25. children: [
  26. {
  27. id: "0-0-1-0"
  28. },
  29. {
  30. id: "0-0-1-1"
  31. }
  32. ]
  33. }
  34. ]
  35. }
  36. ];
  37. class Demo extends React.Component {
  38. constructor(props) {
  39. super(props);
  40. this.state = {
  41. value: "",
  42. expandedKeys: ["0-0"],
  43. autoExpandParent: true
  44. };
  45. this.matchedKeys = [];
  46. this.handleSearch = this.handleSearch.bind(this);
  47. this.handleExpand = this.handleExpand.bind(this);
  48. }
  49. handleSearch(result) {
  50. const value = result.key;
  51. const matchedKeys = [];
  52. const loop = data =>
  53. data.forEach(item => {
  54. if (item.id.indexOf(value.trim()) > -1) {
  55. matchedKeys.push(item.id);
  56. }
  57. if (item.children && item.children.length) {
  58. loop(item.children);
  59. }
  60. });
  61. loop(data);
  62. this.setState({
  63. value: result.key,
  64. expandedKeys: matchedKeys,
  65. autoExpandParent: true
  66. });
  67. this.matchedKeys = matchedKeys;
  68. }
  69. handleExpand(keys) {
  70. this.setState({
  71. expandedKeys: keys,
  72. autoExpandParent: false
  73. });
  74. }
  75. render() {
  76. const loop = data =>
  77. data.map(item => {
  78. return (
  79. <TreeNode label={item.id} key={item.id}>
  80. {item.children && loop(item.children)}
  81. </TreeNode>
  82. );
  83. });
  84. const { value, expandedKeys, autoExpandParent } = this.state;
  85. const filterTreeNode = node =>
  86. value && this.matchedKeys.indexOf(node.props.eventKey) > -1;
  87. return (
  88. <div>
  89. <Search
  90. type="normal"
  91. size="medium"
  92. searchText=""
  93. value={value}
  94. onSearch={this.handleSearch}
  95. />
  96. <Tree
  97. expandedKeys={expandedKeys}
  98. autoExpandParent={autoExpandParent}
  99. filterTreeNode={filterTreeNode}
  100. onExpand={this.handleExpand}
  101. >
  102. {loop(data)}
  103. </Tree>
  104. </div>
  105. );
  106. }
  107. }
  108. ReactDOM.render(<Demo />, mountNode);

相关区块

Tree 树形控件 - 图8

暂无相关区块