背景:产品经理的一个“小需求”
那天下午,产品经理端着枸杞茶晃悠到我工位:
"咱们那个任务管理系统啊,父任务下要支持无限层级子任务,用户说操作完经常要手动刷新才能看到变化..."
我表面笑嘻嘻点开 Element Plus 文档,内心 OS:不就是个树形表格懒加载吗?洒洒水啦~
第一回合:天真的我 vs 倔强的 el-table
随手撸出标准写法:
<el-table
:data="tableData"
lazy
:load="loadChild"
row-key="taskId"
ref="tableRef"
>
<!-- 列定义 -->
</el-table>
接口对接完毕,增删改查一气呵成!
但很快,噩梦来了:
- ✅ 新增子项:父节点秒收合再展开(用户:这动画晃得我头晕)
- ✅ 删除子项:隔壁兄弟节点突然自闭(节点:我当时害怕极了)
- ✅ 修改数据:看心情决定是否刷新(我:你礼貌吗?)
第二回合:与 Element Plus 源码的激情对线
翻源码的时候,我发现 lazyTreeNodeMap
这个内部状态对象掌控着懒加载节点的生杀大权,而 Element Plus 并没有给我们暴露一个合适的 API 来精细化管理它。
问题来了:
- 每次数据变更后,el-table 只会“记住”旧数据,不会主动重新加载子节点!
- 直接修改
lazyTreeNodeMap
会导致展开状态丢失,甚至触发表格异常!
这时候,我意识到得自己动手封装一个合理的缓存管理方案。
第三回合:科学疗法——自研懒加载管理方案
1. 设计一个缓存管理器
首先,我们用 Map
来存储 el-table
懒加载的节点信息,以便后续操作:
const loadNodeMap = new Map<string, { row: any; treeNode: any; resolve: Function }>();
2. 统一的父节点刷新方法
当子节点数据发生变化时,我们需要精准更新对应的 lazyTreeNodeMap
,而不是暴力刷新整个表格。
const refreshParentNode = async (parentId: string) => {
const parentLoadInfo = loadNodeMap.get(parentId);
if (!parentLoadInfo) return;
const { row, treeNode, resolve } = parentLoadInfo;
// 触发缓存更新
if (tableRef.value?.store?.states.lazyTreeNodeMap?.value) {
tableRef.value.store.states.lazyTreeNodeMap.value[parentId][0]['loadState'] = '';
}
// 重新加载数据
await loadChild(row, treeNode, resolve);
};
思路解析:
- 先取缓存,确保
parentId
存在- 手动触发懒加载缓存更新,避免
el-table
继续使用旧数据- 重新加载子节点,让
el-table
正确渲染最新数据
3. 实现 loadChild
方法
const loadChild = async (row: any, treeNode: any, resolve: Function) => {
try {
const parentId = row.taskId; // 以 taskId 作为 parentId
const res = await taskChildListApi({ projectId: props.projectId, parentId });
if (res.code === 200) {
const children = reactive(res.rows);
resolve(children || []);
row.children = children;
loadNodeMap.set(row.taskId, { row, treeNode, resolve });
} else {
resolve([]);
row.children = [];
}
} catch (error) {
PageUtil.msgError('加载子任务列表出错');
resolve([]);
}
};
这里的关键点:
- 接口调用后,直接
resolve(children)
让el-table
识别新数据- 存入
loadNodeMap
,方便后续精准更新
终极优化:防止频繁触发刷新
由于用户操作频繁,我们可以给 refreshParentNode
加一个 debounce
,防止高频调用:
import { debounce } from 'lodash';
const safeRefresh = debounce(refreshParentNode, 300);
这样一来,即使用户疯狂点击,系统也不会频繁触发 API 请求,性能大大提升!
血泪总结:Element Plus 树表生存指南
-
懒加载缓存三原则:
- 改缓存前先拍照(存展开状态)
- 动刀只切病变部位(精准更新
lazyTreeNodeMap
) - 完事儿记得恢复现场(
nextTick
大法)
-
性能优化骚操作:
// 给频繁操作上减速带 import { debounce } from 'lodash'; const safeRefresh = debounce(refreshParentNode, 300);
-
兜底方案:
// 被逼急了的终极奥义(慎用!) tableRef.value?.doLayout(); // 强制重绘
后记
当我把这个方案提交后,产品经理看着丝般顺滑的更新效果,终于放下了他 40 米长的需求大刀。而我在深夜的办公室,默默删掉了准备提交的离职申请...