tree组件的halfChecked值应该后端存储还是前端计算?

背景

昨天在做一个树形组件权限控制的功能,包括菜单和按钮的访问权限。
熟悉 ant design 组件库 tree 组件的都知道,如果一个父节点,他的子节点存在未选中的,那么这个父节点应该是一个半选(半选可以直观表示其子节点存在未选中)的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const treeData = [
{
title: '0-0',
key: '0-0',
children: [
{
title: '0-0-0',
key: '0-0-0',
children: [
{
title: '0-0-0-0',
key: '0-0-0-0',
},
{
title: '0-0-0-1',
key: '0-0-0-1',
},
{
title: '0-0-0-2',
key: '0-0-0-2',
},
],
},
],
},
];

如果节点 0-0-0-0 未选中,那么此时实际权限为 ['0-0-0-1', '0-0-0-2', '0-0-0', '0-0'],tree 组件为了父节点显示为半选效果,tree.checkedKeys 应该为 ['0-0-0-1', '0-0-0-2']

tree 组件 onCheck 事件可以得到 checkedKeys: ['0-0-0-1', '0-0-0-2']halfCheckedKeys: ['0-0-0', '0-0']

最简单的方案是前端传一个实际权限数组 permissionsList 和一个过滤掉半选状态的数组 filteredCheckedKeys 给后端,后端再返回给前端做回显即可。如下

1
2
const permissionsList = [...checkedKeys, ...halfCheckedKeys];
const filteredCheckedKeys = [...checkedKeys];

问题来了,后端觉得没必要保存两个字段,他只希望接收 permissionsList。后端如果不处理,只能前端计算 filteredCheckedKeys

考虑到用户量和权限树节点比较少,前端算就前端算,chatGpt 三下五除二就搞定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// Function to recursively calculate halfCheckedKeys
function getHalfCheckedKeys(treeData, checkedKeys) {
const halfChecked = [];

// Helper function to recursively traverse the tree
function traverse(node) {
if (!node.children || node.children.length === 0) {
// Leaf node: Return if it's checked or not
return checkedKeys.includes(node.key) ? 'checked' : 'unchecked';
}

let allChecked = true;
let anyChecked = false;

// Recursively check all child nodes
node.children.forEach((child) => {
const childStatus = traverse(child);

if (childStatus === 'checked') {
anyChecked = true;
} else if (childStatus === 'unchecked') {
allChecked = false;
} else if (childStatus === 'half-checked') {
anyChecked = true;
allChecked = false;
}
});

if (allChecked) {
return 'checked';
} else if (anyChecked) {
halfChecked.push(node.key);
return 'half-checked';
} else {
return 'unchecked';
}
}

// Start traversing the tree
treeData.forEach(traverse);

return halfChecked;
}

// 示例已选中的 keys
const permissionsList = ['0-0-0-1', '0-0-0-2', '0-0-0', '0-0'];

// 获取 halfCheckedKeys
const halfCheckedKeys = getHalfCheckedKeys(treeData, permissionsList);

console.log('Half Checked Keys:', halfCheckedKeys); // 输出: ['0-0-0', '0-0']

// 过滤 permissionsList,排除 halfCheckedKeys 中的键
const filteredCheckedKeys = permissionsList.filter(
(key) => !halfCheckedKeys.includes(key),
);

到此,前端多了一步计算 getHalfCheckedKeys, 后端少存了一个字段。

但我不想就此结束,可以前端写,但前端不能没理由的写,于是我问了 chatgpt 两个方案的差异,如下:

总结对比

特性 前端动态计算 halfCheckedKeys 后端存储 halfCheckedKeys
性能 对大树结构可能性能较差 性能较好,前端无需计算
实现复杂度 前端需要复杂的递归计算 后端存储和同步逻辑复杂
一致性 基于前端状态,完全一致 依赖于前后端的状态同步
灵活性 可随时重新计算,无需后端依赖 必须依赖后端返回,灵活性较低
维护成本 代码复杂且随应用增长增加维护难度 增加存储和同步逻辑,前后端需同时维护
数据管理 前端本地管理,无需后端存储 后端需存储和管理 halfCheckedKeys

适用场景

方案一(前端动态计算)适用于

  • 权限树结构较小、节点较少时。
  • 前端有足够的性能来处理权限树。
  • 需要高交互性、即时反馈的场景,减少与后端的交互次数。

方案二(后端存储)适用于

  • 权限树结构非常庞大、节点数量较多时。
  • 前端性能较低或应用规模较大,减少前端计算压力。
  • 希望减少前端逻辑复杂度,后端统一管理和处理数据的场景。

最佳实践

  • 如果权限树数据规模较大,或者操作频繁,方案二(后端存储 halfCheckedKeys)会更具优势,提升前端性能和简化实现。
  • 如果权限树较小或者需要灵活性、即时反馈,方案一(前端动态计算)可能更适合,并且减少了后端的复杂性。

后面又聊着聊着,后端也发现了还是后端存储这种方案会比较简单。如果这个权限树节点很多,getHalfCheckedKeys 的计算可能会导致前端页面卡顿。

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×