需求
在树状结构的数据显示中,未知层数。均存在选择框,需要做勾选逻辑的处理。规则如下
- 勾选有三种状态,全勾、半勾、不勾。
- 当某个节点勾选并且有子节点时,将当前节点勾上,并且将其所有的子节点全部勾选。
- 当某个节点勾选并且是作为子节点时,若兄弟节点均勾选上,则将父节点全勾;若兄弟节点只勾选一部分,则父节点半勾。
- 以上两条规则除了考虑勾选逻辑,同时需要考虑取消勾选的逻辑。
分析
当前已有的数据结构list如下,checked作为勾选标志,Y为全勾,P为半勾,N为不勾。每个节点均有可能存在子节点,也可能为空。
[{
id: 1,
parentId: null,
checked: 'P',
children: [{
id: 4,
parentId: 1,
checked: 'Y',
children: [...],
}, {
id: 5,
parentId: 1,
checked: 'P',
children: [...],
}],
}, {
id: 2,
parentId: null,
checked: 'Y'
children: [...],
}, {
id: 3,
parentId: null,
checked: 'N'
children: [...],
}]
这里有个特别的地方是需要对父节点的内容做操作,而由于目前的数据结构从子节点去获取父节点其实是比较困难的,所以添加一个map的数据结构,用来方便子节点对父节点的获取。map的数据结果如下。
Map() {
1: {
id: 1,
parentId: null,
checked: 'P',
children: [...],
},
2: {
id: 2,
parentId: null,
checked: 'Y'
children: [...],
},
3: {
id: 3,
parentId: null,
checked: 'N'
children: [...],
},
4: {
id: 4,
parentId: 1,
checked: 'Y',
children: [...],
},
...
}
因为在生成map的过程不做克隆操作,而是直接赋值地址空间,所以map中value指向的对象和list中指向的对象是相同的,并没有占用多余的空间。这一个生成的过程需要使用递归。
实现
实现关键的部分,首先是通过list去生成一个map的对象。
map = new Map();
getMap(list){
if(list && list.length>0){
list.forEach(item => {
map.set(item.id, item);
getMap(item.children); // 递归调用
})
}
}
勾选的时候,调用的是 handleCheck 方法。
// 对子节点做处理
handleChildren(record, checked){
record.checked = checked ? 'Y' : 'N'; // 对当前节点赋值,再遍历其子节点
if(record.children && record.children.length > 0){
record.children.forEach(item => handleChildren(item, checked)); // 递归调用
}
}
// 对父节点的处理
handleParent(record){
// 看所有子节点的状态。
let checkAll = true;
let uncheckAll = true;
const lastChecked = record.checked; // 记录原来的状态
record.children.forEach(item => {
// 当不全勾选时,则 checkAll 为false
if(item.checked === 'P' || item.checked === 'N')
checkAll = false;
// 当至少有一个勾选的时,uncheckAll为false
if(item.checked === 'P' || item.checked === 'Y')
uncheckAll = false;
});
if(checkAll){ // 全选则为 Y
record.checked = 'Y';
} else if(uncheckAll){ // 全不选则为 N
record.checked = 'N';
} else { // 不全选则为 P
record.checked = 'P';
}
if(record.parentId && lastChecked!==record.checked){
// 父节点存在并且当前节点的状态改变了,才需要继续往父节点操作。
const parentNode = map.get(record.parentId);
handleParent(parentNode); // 递归调用
}
}
// 勾选的调用方法
handleCheck(id, checked){
const record = map.get(id); // 获取当前操作的对象
// 先对子节点做遍历并勾选
handleChildren(record, checked);
// 然后处理对父节点的影响
if(record.parentId) { // 存在则处理
const parentNode = map.get(record.parentId);
handleParent(parentNode);
}
// 最后统一做界面更新,设置state
// ...
}