
Element UI Tree 树形控件完全指南数据绑定、懒加载、勾选联动与拖拽实战【免费下载链接】elementA Vue.js 2.0 UI Toolkit for Web项目地址: https://gitcode.com/gh_mirrors/eleme/element导读本文以 Element UIVue.js 2.0 UI Toolkit官方文档中的 Tree 树形控件为主题系统讲解其在 packages/tree 下的完整用法从最基本的数据渲染到懒加载、复选框联动、默认展开/勾选、节点增删改查、自定义节点内容、过滤、手风琴与拖拽排序。文中所有示例均可直接复制运行并结合packages/tree/src的源码实现tree.vue、model/node.js、model/tree-store.js与 test/unit/specs/tree.spec.js 单元测试说明每个参数与方法的底层行为。读完本文你将能够熟练使用el-tree搭建从简单目录树到支持异步加载、勾选、拖拽的复杂业务树。基本使用渲染一棵最简单的树el-tree的核心输入是data树数据数组与props字段映射配置。props用于告诉组件数据对象中哪个字段代表显示文本label、哪个字段代表子节点children。el-tree :datadata :propsdefaultProps node-clickhandleNodeClick/el-tree script export default { data() { return { data: [{ label: Level one 1, children: [{ label: Level two 1-1, children: [{ label: Level three 1-1-1 }] }] }, { label: Level one 2, children: [{ label: Level two 2-1, children: [{ label: Level three 2-1-1 }] }, { label: Level two 2-2, children: [{ label: Level three 2-2-1 }] }] }], defaultProps: { children: children, label: label } }; }, methods: { handleNodeClick(data) { console.log(data); } } }; /scriptnode-click回调的第一个参数是该节点对应的data对象可用于跳转、回填表单等业务逻辑。从源码看el-tree根组件在created钩子中创建了TreeStore与根节点root根节点通过Node.setData递归地把data数组转成内部Node对象树见 node.jsprops的默认值为{ children: children, label: label, disabled: disabled }见 tree.vue即字段名可完全自定义。可勾选与异步加载lazy loadTree 支持节点复选框并通过load方法实现按需异步加载子节点el-tree :propsprops :loadloadNode lazy show-checkbox check-changehandleCheckChange /el-tree script export default { data() { return { props: { label: name, children: zones }, count: 1 }; }, methods: { handleCheckChange(data, checked, indeterminate) { console.log(data, checked, indeterminate); }, loadNode(node, resolve) { if (node.level 0) { return resolve([{ name: Root1 }, { name: Root2 }]); } if (node.level 3) return resolve([]); var hasChild Math.random() 0.5; setTimeout(() { var data; if (hasChild) { data [{ name: zone this.count }, { name: zone this.count }]; } else { data []; } resolve(data); }, 500); } } }; /script关键点lazy开启懒加载模式load接收(node, resolve)node.level为节点层级根节点 level 为 0resolve必须被调用以返回子节点数据check-change的三个参数依次是节点数据、是否勾选、是否半选indeterminate即子节点被部分勾选。源码层面懒加载的入口在Node.shouldLoadData()当store.lazy true store.load !this.loaded时首次展开或勾选会触发loadData内部通过this.store.load(this, resolve)回调拿到子数组后再doCreateChildren创建子节点并标记loaded true见 node.js、node.js。懒加载下的叶子节点判定isLeaf在懒加载模式下节点数据未加载前组件无法预知是否为叶子因此默认会给每个节点都显示展开箭头点击后才根据返回数据判断。若想提前告知该节点是叶子可通过props.isLeaf声明el-tree :propsprops :loadloadNode lazy show-checkbox/el-tree script export default { data() { return { props: { label: name, children: zones, isLeaf: leaf } }; }, methods: { loadNode(node, resolve) { if (node.level 0) { return resolve([{ name: region }]); } if (node.level 1) return resolve([]); setTimeout(() { const data [{ name: leaf, leaf: true }, { name: zone }]; resolve(data); }, 500); } } }; /script其中leaf: true的节点将不再显示展开箭头。源码中Node构造时读取props.isLeaf并存入isLeafByUserupdateLeafState()在懒加载未完成时优先采用该标记判断叶子状态见 node.js、node.js。禁用部分节点的复选框在props中声明disabled字段数据对象里带disabled: true的节点复选框将被禁用且不可点击el-tree :datadata :propsdefaultProps show-checkbox check-changehandleCheckChange/el-tree script export default { data() { return { data: [{ id: 1, label: Level one 1, children: [{ id: 3, label: Level two 2-1, children: [{ id: 4, label: Level three 3-1-1 }, { id: 5, label: Level three 3-1-2, disabled: true }] }, { id: 2, label: Level two 2-2, disabled: true, children: [{ id: 6, label: Level three 3-2-1 }] }] }], defaultProps: { children: children, label: label, disabled: disabled } }; } }; /script源码中Node.disabled是一个 getter通过getPropertyFromData(this, disabled)从数据中读取而getPropertyFromData支持字符串字段名或函数两种形式见 node.js。即disabled也可写成函数(data, node) boolean做动态判断。渲染层由 tree-node.vue 中的el-checkbox :disabled!!node.disabled实现禁用。默认展开与默认勾选default-expanded-keys / default-checked-keys通过default-expanded-keys与default-checked-keys分别指定初始展开与初始勾选的节点。前提是必须设置node-key其值为数据对象中某个键名且该键值必须在整棵树中唯一el-tree :datadata show-checkbox node-keyid :default-expanded-keys[2, 3] :default-checked-keys[5] :propsdefaultProps /el-tree底层行为节点构造时若node-key存在且其值命中defaultExpandedKeys会调用expand(null, store.autoExpandParent)见 node.js初始勾选通过_initDefaultCheckedNodes遍历defaultCheckedKeys找到对应节点并执行setChecked(true, !this.checkStrictly)见 tree-store.jscheckStrictly为false默认时会联动影响父节点状态。通过实例方法读取与设置勾选状态通过ref拿到树实例后可调用getCheckedNodes/getCheckedKeys/setCheckedNodes/setCheckedKeys等方法按节点或按 key 读写勾选el-tree :datadata show-checkbox default-expand-all node-keyid reftree highlight-current :propsdefaultProps /el-tree div classbuttons el-button clickgetCheckedNodesget by node/el-button el-button clickgetCheckedKeysget by key/el-button el-button clicksetCheckedNodesset by node/el-button el-button clicksetCheckedKeysset by key/el-button el-button clickresetCheckedreset/el-button /div script export default { methods: { getCheckedNodes() { console.log(this.$refs.tree.getCheckedNodes()); }, getCheckedKeys() { console.log(this.$refs.tree.getCheckedKeys()); }, setCheckedNodes() { this.$refs.tree.setCheckedNodes([{ id: 5, label: Level two 2-1 }, { id: 9, label: Level three 1-1-1 }]); }, setCheckedKeys() { this.$refs.tree.setCheckedKeys([3]); }, resetChecked() { this.$refs.tree.setCheckedKeys([]); } } }; /script源码解析getCheckedNodes(leafOnly, includeHalfChecked)在 tree-store.js 中递归遍历leafOnly为true时只返回叶子节点includeHalfChecked为true时把半选节点一并返回getCheckedKeys(leafOnly)基于getCheckedNodes再映射出node-key数组见 tree-store.jssetCheckedKeys/setCheckedNodes内部统一走_setCheckedKeys先按层级从深到浅排序重置所有节点再对命中 key 的节点沿父链标记缓存并setChecked(true, true)联动子树见 tree-store.js。这些行为都有对应单测覆盖例如setCheckedKeys、setCheckedNodes、getCurrentKey等用例见 tree.spec.js。自定义节点内容render-content 与 scoped slot节点内容可完全自定义支持两种写法div classcustom-tree-container div classblock pUsing render-content/p el-tree :datadata show-checkbox node-keyid default-expand-all :expand-on-click-nodefalse :render-contentrenderContent /el-tree /div div classblock pUsing scoped slot/p el-tree :datadata show-checkbox node-keyid default-expand-all :expand-on-click-nodefalse span classcustom-tree-node slot-scope{ node, data } span{{ node.label }}/span span el-button typetext sizemini click() append(data)Append/el-button el-button typetext sizemini click() remove(node, data)Delete/el-button /span /span /el-tree /div /div script let id 1000; export default { data() { const data [{ id: 1, label: Level one 1, children: [{ id: 4, label: Level two 1-1, children: [{ id: 9, label: Level three 1-1-1 }] }] }]; return { data: JSON.parse(JSON.stringify(data)) }; }, methods: { append(data) { const newChild { id: id, label: testtest, children: [] }; if (!data.children) { this.$set(data, children, []); } data.children.push(newChild); }, remove(node, data) { const parent node.parent; const children parent.data.children || parent.data; const index children.findIndex(d d.id data.id); children.splice(index, 1); }, renderContent(h, { node, data, store }) { return ( span classcustom-tree-node span{node.label}/span span el-button sizemini typetext on-click{ () this.append(data) }Append/el-button el-button sizemini typetext on-click{ () this.remove(node, data) }Delete/el-button /span /span); } } }; /script style .custom-tree-node { flex: 1; display: flex; align-items: center; justify-content: space-between; font-size: 14px; padding-right: 8px; } /style两种方式的优先级与渲染细节内部组件NodeContent的 render 逻辑是先render-content再$scopedSlots.default都没有则渲染node.label见 tree-node.vue。render-content需要支持 JSX 的构建环境scoped slot 的node是内部TreeNode实例含label、parent、data等data是节点原始数据。remove中通过node.parent找到父节点并 splice 掉自身正是利用了 TreeNode 的内部结构源码中Node同样提供removeChild/insertChild等能力见 node.js。节点过滤filter filter-node-method配合el-input实现实时过滤核心是调用树实例的filter方法并传入filter-node-method作为过滤函数el-input placeholderFilter keyword v-modelfilterText/el-input el-tree classfilter-tree :datadata :propsdefaultProps default-expand-all :filter-node-methodfilterNode reftree /el-tree script export default { watch: { filterText(val) { this.$refs.tree.filter(val); } }, methods: { filterNode(value, data) { if (!value) return true; return data.label.indexOf(value) ! -1; } } }; /scriptfilter(value)内部调用this.store.filter(value)见 tree.vuestore.filter会深度遍历所有子节点用filterNodeMethod.call(child, value, child.data, child)的结果设置child.visible若某父节点下所有子节点都不可见父节点也会被隐藏非懒加载模式下命中节点会自动展开见 tree-store.js。对应的单测为filter-node-method见 tree.spec.js。手风琴模式accordion设置accordion后同一层级同一时刻只允许展开一个节点el-tree :datadata :propsdefaultProps accordion node-clickhandleNodeClick/el-tree实现机制每个ElTreeNode在created时若检测到tree.accordion会监听tree-node-expand广播事件把非自身的兄弟节点折叠见 tree-node.vue事件由根组件在handleNodeExpand中通过 emitter mixin 广播见 tree.vue。拖拽排序draggable给el-tree加上draggable即可启用拖拽配合allow-drag/allow-drop做拖拽约束并通过一系列node-drag-*与node-drop事件感知全过程el-tree :datadata node-keyid default-expand-all node-drag-starthandleDragStart node-drag-enterhandleDragEnter node-drag-leavehandleDragLeave node-drag-overhandleDragOver node-drag-endhandleDragEnd node-drophandleDrop draggable :allow-dropallowDrop :allow-dragallowDrag /el-tree script export default { methods: { handleDragStart(node, ev) { console.log(drag start, node); }, handleDragEnter(draggingNode, dropNode, ev) { console.log(tree drag enter: , dropNode.label); }, handleDragLeave(draggingNode, dropNode, ev) { console.log(tree drag leave: , dropNode.label); }, handleDragOver(draggingNode, dropNode, ev) { console.log(tree drag over: , dropNode.label); }, handleDragEnd(draggingNode, dropNode, dropType, ev) { console.log(tree drag end: , dropNode dropNode.label, dropType); }, handleDrop(draggingNode, dropNode, dropType, ev) { console.log(tree drop: , dropNode.label, dropType); }, allowDrop(draggingNode, dropNode, type) { if (dropNode.data.label Level two 3-1) { return type ! inner; } else { return true; } }, allowDrag(draggingNode) { return draggingNode.data.label.indexOf(Level three 3-1-1) -1; } } }; /scriptallowDrop的type有三种取值prev放在目标节点前、inner放入目标节点内部成为子节点、next放在目标节点后。根组件通过tree-node-drag-*内部事件计算dropType根据鼠标在目标节点高度上的位置比例判定 before / inner / after并调用allowDrop(draggingNode.node, dropNode.node, type)校验落点有效时通过insertBefore/insertAfter/insertChild完成结构变更见 tree.vue。属性Attributes总表属性说明类型默认值data展示数据array—empty-text数据为空时的提示文本string—node-key每个树节点用来作为唯一标识的属性整棵树唯一string—props配置选项object—render-after-expand是否在第一次展开某个树节点后才渲染其子节点booleantrueload加载子树数据的方法仅当 lazy 为 true 时生效function(node, resolve)—render-content树节点的内容区的渲染 FunctionFunction(h, { node, data, store })—highlight-current是否高亮当前选中节点booleanfalsedefault-expand-all是否默认展开所有节点booleanfalseexpand-on-click-node是否在点击节点的时候展开或者收缩节点默认值为 true如果为 false则只有点箭头图标的时候才会展开或者收缩节点booleantruecheck-on-click-node是否在点击节点的时候选中节点默认值为 false只有在点击复选框的时候才会选中节点booleanfalseauto-expand-parent展开子节点的时候是否自动展开父节点booleantruedefault-expanded-keys默认展开的节点的 key 的数组array—show-checkbox节点是否可被选择booleanfalsecheck-strictly在显示复选框的情况下是否严格的遵循父子不互相关联的做法默认为 falsebooleanfalsedefault-checked-keys默认勾选的节点的 key 的数组array—current-node-key当前选中的节点string, number—filter-node-method对树节点进行筛选时执行的方法返回 false 表示这个节点会被隐藏Function(value, data, node)—accordion是否每次只打开一个同级树节点展开booleanfalseindent相邻级节点之间的水平缩进单位为像素number16源码中 tree.vue 的实际默认值为 18icon-class自定义树节点的图标string—lazy是否懒加载子节点需与 load 方法结合使用booleanfalsedraggable是否开启拖拽节点功能booleanfalseallow-drag判断节点能否被拖拽返回 false 表示该节点不可拖拽Function(node)—allow-drop拖拽时判定目标节点能否被放置返回 false 表示不能放置。type取值prev放置到目标节点前、inner放置到目标节点内部、next放置到目标节点后Function(draggingNode, dropNode, type)—需要说明的是官方文档中indent标注默认值为 16而当前仓库源码 tree.vue 中定义的默认值为 18实际渲染以源码为准缩进应用于 tree-node.vue 的padding-left: (node.level - 1) * tree.indent。props 字段映射说明属性说明类型label指定节点标签为节点对象的某个属性值string, function(data, node)children指定子树为节点对象的某个属性值stringisLeaf指定节点是否为叶子节点仅在指定了 lazy 属性的情况下生效boolean, function(data, node)三者均支持函数形式例如label: (data, node) data.name - data.id源码中getPropertyFromData会先判断配置是否为函数并调用之见 node.js。实例方法Methods总表除特别说明外多数方法要求设置node-key。方法说明参数filter对树节点进行筛选操作隐藏不符合要求的节点接收一个参数将作为 filter-node-method 的第一个参数updateKeyChildren通过 key 获取某个节点设置该节点的数据要求 node-key 已设置(key, data)getCheckedNodes若节点可被选择show-checkbox 为 true返回目前被选中的节点所组成的数组(leafOnly, includeHalfChecked) 两个布尔值参数均默认 falsesetCheckedNodes设置目前勾选的节点要求 node-key 已设置一个数组元素为要勾选的节点数据getCheckedKeys若节点可被选择show-checkbox 为 true返回目前被选中的节点的 key 所组成的数组(leafOnly) 布尔值默认 falsesetCheckedKeys通过 keys 设置目前勾选的节点要求 node-key 已设置(keys, leafOnly)setChecked通过 key / data 设置某个节点的勾选状态要求 node-key 已设置(key/data, checked, deep)getHalfCheckedNodes若节点可被选择show-checkbox 为 true返回目前半选中的节点所组成的数组—getHalfCheckedKeys若节点可被选择show-checkbox 为 true返回目前半选中的节点的 key 所组成的数组—getCurrentKey获取当前被选中节点的 key若没有则返回 null—getCurrentNode获取当前被选中节点的数据若没有则返回 null—setCurrentKey通过 key 设置某个节点的当前选中状态要求 node-key 已设置key 为 null 时取消当前高亮(key)setCurrentNode通过 node 设置某个节点的当前选中状态要求 node-key 已设置(node)getNode根据 data 或 key 拿到 TreeNode 对象(data)remove删除节点要求 node-key 已设置(data) 节点数据或节点append为某个节点追加子节点(data, parentNode)insertBefore在指定节点前插入节点(data, refNode)insertAfter在指定节点后插入节点(data, refNode)补充说明读写勾选类的底层逻辑集中在 tree-store.jsgetHalfCheckedNodes通过遍历child.indeterminate收集半选节点append/insertBefore/insertAfter/remove均通过store定位节点后操作内部Node树并同步维护nodesMap索引见 tree-store.js部分方法在未设置node-key时会显式抛出[Tree] nodeKey is required in xxx错误见 tree.vue这也是按 key 操作必须配置 node-key的源码依据。事件Events总表事件名称说明回调参数node-click节点被点击时的回调(nodeData, node, TreeNodeComponent)节点数据、TreeNode 的 data 属性、TreeNode 组件实例node-contextmenu当某个节点被右键点击时会触发该事件(event, nodeData, node, TreeNodeComponent)check-change节点选中状态发生变化时的回调(nodeData, checked, indeterminate)节点数据、勾选状态、子节点部分勾选状态check当复选框被点击的时候触发(nodeData, checkedInfo)checkedInfo 含 checkedNodes、checkedKeys、halfCheckedNodes、halfCheckedKeys 四个属性current-change当前选中节点变化时触发(nodeData, node)node-expand节点被展开时触发(nodeData, node, TreeNodeComponent)node-collapse节点被收起时触发(nodeData, node, TreeNodeComponent)node-drag-start节点开始拖拽时触发(node, event)node-drag-enter拖拽进入其他节点时触发(draggingNode, dropNode, event)node-drag-leave拖拽离开某个节点时触发(draggingNode, dropNode, event)node-drag-over在拖拽节点时触发类似 mouseover 事件(draggingNode, dropNode, event)node-drag-end拖拽结束时可能未成功触发(draggingNode, dropNode, dropType, event)dropType 为 before / after / innerdropNode 可能为 undefinednode-drop拖拽成功完成时触发(draggingNode, dropNode, dropType, event)事件触发细节check事件在handleCheckChange中通过$nextTick后以store.getCheckedNodes()等组装完整勾选状态对象再发出见 tree-node.vuenode-expand/node-collapse由展开图标点击逻辑发出同时会通过 emitter 广播tree-node-expand以支持手风琴模式见 tree-node.vue。Scoped Slot名称说明—默认插槽自定义树节点的内容作用域参数为{ node, data }node为 TreeNode 实例data为节点原始数据小结Element UI 的el-tree是一套数据驱动、能力完备的树形控件dataprops解决数据接入node-key是所有按 key 操作的前提lazyloadisLeaf支撑大数据量异步渲染show-checkbox配合勾选联动算法父子关联、半选、严格模式check-strictly满足权限树等场景render-content/ scoped slot 提供无限定制filter、accordion、draggable则覆盖检索、导航与排序需求。结合 packages/tree/src 下的源码与 test/unit/specs/tree.spec.js 中的 30 余条测试用例可以更深入地理解其内部 Node 树模型与状态同步机制从而在真实业务中正确选型与排错。【免费下载链接】elementA Vue.js 2.0 UI Toolkit for Web项目地址: https://gitcode.com/gh_mirrors/eleme/element创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考