Element Plus Tree 组件跨树操作获取不到node的问题

前言

在使用 Element Plus Tree 组件时,我遇到了一个看似简单但极其困惑的问题:当我通过 append 方法将数据添加到目标树后,立即调用 getNode(data) 却返回null。这个问题困扰了我很久,经过深入的调试和源码分析,最终发现了问题的根本原因。

问题描述

业务场景

我的应用中有两个 Tree 组件:

  • sourceTree:显示可选的工作列表
  • targetTree:显示已选的工作列表

用户可以从 sourceTree 中选择工作,然后将其添加到 targetTree 中。同时,用户也可以从 targetTree 中删除已选的工作。

问题现象

// 添加数据到目标树
targetTree.value?.append(data, parent);

// 立即尝试获取节点 - 返回 null!
const node = targetTree.value?.getNode(data);
console.log(node); // null

奇怪的是:

  1. 数据确实被添加到了树中(nodesMap 中确实有新节点,nodesMap是el-tree实例内部的一个状态)
  2. 同样的 getNode(data) 在 sourceTree 中工作正常
  3. 官方文档明确说明 getNode 可以传递 nodeData 或 nodeKey

初步分析与错误尝试

尝试1:怀疑是异步更新问题

  // 使用 nextTick 等待更新
  await nextTick();
  const node = targetTree.value?.getNode(data);
  console.log(node); // 仍然是 null!

尝试2:怀疑是 node-key 问题

根据网上的一些资料,我尝试设置 node-key:

  <el-tree
    :data="targetData"
    node-key="workContent"
    ref="targetTree"
  >

但是不行,因为我的targetData是混合数据结构,每一阶的数据结构都不一样,而element-plus官方提供的node-key属性只能传递一个string

尝试3:添加唯一 ID

  // 为数据添加唯一标识
  if (!data.id) {
    data.id = `work_${data.typeID}_${data.apInterventionID}_${Date.now()}`;
  }

破坏了原有的数据结构,导致树不显示,而且复杂度明显提高,不方便维护

深入源码分析

getNode 方法的实现

通过分析 Element Plus 源码,我发现了 getNode 方法的关键实现:

  // tree-store.js
  getNode(data) {
    if (data instanceof Node) return data;
    const key = isObject(data) ? getNodeKey(this.key, data) : data;
    return this.nodesMap[key] || null;
  }

getNodeKey 的关键逻辑

  // util.js
  const getNodeKey = function(key, data) {
    if (!key) return data[NODE_KEY];  // NODE_KEY = "$treeNodeId"
    return data[key];
  };

关键发现:

  • 当没有设置 node-key 时,getNode 依赖于 data.$treeNodeId 属性
  • 这个属性是通过 markNodeData 函数添加的隐藏属性

markNodeData 的实现

  // util.js
  const markNodeData = function(node, data) {
    if (!data || data[NODE_KEY]) return;
    Object.defineProperty(data, NODE_KEY, {
      value: node.id,
      enumerable: false,
      configurable: false,
      writable: false
    });
  };

问题根源的发现

调试验证

为了验证我的推测,我添加了调试代码:

targetTree.value?.append(data, parent);
// 验证我的分析
console.log("data对象的$treeNodeId:", data.$treeNodeId);
console.log("targetTree的nodesMap keys:", Object.keys(targetTree.value?.store.nodesMap || {}));
console.log("是否匹配:", Object.keys(targetTree.value?.store.nodesMap || {}).includes(String(data.$treeNodeId)));

调试结果

  data对象的$treeNodeId: 8
  targetTree的nodesMap keys: ['358', '359', '360', '361', '362', '363', '364', '365', '366']
  是否匹配: false

答案很明显了,问题的根本原因,就是当我从 sourceTree 移动数据到 targetTree 时:

  1. data 对象保留了 sourceTree 的 $treeNodeId = 8
  2. 但 targetTree 中新建的节点 ID 是 366
  3. 所以 getNode(data) 查找 $treeNodeId = 8,在 targetTree 中找不到!
    偷懒复用的后果

最终解决方案

方案一

直接操作data,不走el-tree实例

方案二

如果可以的话,在模板中直接拿到node进行操作

方案三(不推荐)

删除旧的内部标记$treeNodeId。但这么做可能会影响原tree,且此状态本身是组件库内部管理的,不合适在外部操作

总结

Element Plus Tree 的内部机制

  • 没有 node-key 时:依赖内部的 $treeNodeId 标记机制
  • 跨树操作时:深拷贝或使用其它方式获取node
  • 推荐做法:始终设置合适的 node-key
posted @ 2025-07-05 19:01  南山有榛  阅读(81)  评论(0)    收藏  举报