场景:
(1)在父组件Parent里监听到数据变化,调用子组件Child暴露出对应的方法;
(2)子组件里切换el-tab时展示onRef树或inRef树(需要深拷贝数据,导致两个树数据一样)
问题:如果两个树中存在相同的父节点 ID(例如从同一接口获取的初始数据),且未对这些父节点做深拷贝,当调用 append 方法时,可能会错误地将子节点添加到两个树的同一父节点引用中。
(3)根据树id更新树节点,而不是通过遍历,避免搜索到某个节点后,做一些编辑就刷新整个列表。
树结构(每个学生节点下再分子节点In内地或者on外地节点):
Student1 》onData1
》InData1
Student2 》Student2-1 》onData2-1
》Student2 -2 》Student2-2-1 》onData2-2-1
》InData2
》InData2
Parent.vue
<template><div><Child ref="childRef"></Child ></div>
</template>
onMounted(() => {loadData();
});function loadData(){
//监听学生增加事件
studentData.addEventListener(studentAdd, (e: Event) => {let dataEvent = e as DataEvent;if(dataEvent){if(childRef.value){//监听事件里获取新增学生信息const studentData= dataEvent.studentData;if(studentData){//增加此数据分别到子组件的onRef树与inRef树里//(此处需要深拷贝,因为节点对象的 parent 引用需要隔离,否则实际共享同一 parent 对象!//此时,向任一树追加子节点时,两个树的父节点 children 数组会共享引用,就是改一个树另一个树也改变,困扰我好久..)childRef.value.addOnNode(deepClone(studentData));childRef.value.addInNode(deepClone(studentData));}} }});//监听学生修改事件studentData.addEventListener(studentUpdate, (e: Event) => {let dataEvent = e as DataEvent;if(dataEvent){if(childRef.value){//监听事件里获取修改学生信息const studentData= dataEvent.studentData;childRef.value.updateChildNode(studentData);}}}});//监听学生移除事件studentData.addEventListener(studentRemove, (e: Event) => {let dataEvent = e as DataEvent;if(dataEvent){if(childRef.value){//监听事件里获取修改学生信息const studentData= dataEvent.studentData;childRef.value..removeChildNode(studentData);}}});//监听外地增加事件
studentData.onData.addEventListener(studentOnAdd, (e: Event) => {let dataEvent = e as DataEvent;if(dataEvent){if(childRef.value){//监听事件里获取新增外地信息const studentData= dataEvent.studentData;if(studentData){childRef.value.addOnNode(studentData);}} }});
....
}
function deepClone(target: any) {// 定义一个变量let result: any;// 如果当前需要深拷贝的是一个对象的话if (typeof target === 'object') {// 如果是一个数组的话if (Array.isArray(target)) {result = []; // 将result赋值为一个数组,并且执行遍历for (let i in target) {// 递归克隆数组中的每一项result.push(this.deepClone(target[i]))}// 判断如果当前的值是null的话;直接赋值为null} else if(target===null) {result = null;// 判断如果当前的值是一个RegExp对象的话,直接赋值 } else if(target.constructor===RegExp){result = target;}else {// 否则是普通对象,直接for in循环,递归赋值对象的所有值result = {};for(let key in target){result[key] = this.deepClone(target[key]);}}// 如果不是对象的话,就是基本数据类型,那么直接赋值} else {result = target;}// 返回最终结果return result;},
Child.vue
<template><div><el-tabs class="tabs" @tab-click="handleClick" v-model="unitModal"><el-tab-pane label="外地" name="on"><el-inputv-model="onFilterText"placeholder="搜索"clearable/><el-scrollbar><el-tree ref="onRef":data="onStudent" node-key="id" :filter-node-method="filterOnNode"default-expand-all ><template #default="{ node, data }"><slot :node="node" :data="data"><img :src="data.icon" style="height: 25px;width: 25px"><span style="margin: 0 10px;">{{ data.name }}</span></slot></template></el-tree></el-scrollbar></el-tab-pane><el-tab-pane label="内地" name="in"><el-inputv-model="inFilterText"placeholder="搜索"/><el-scrollbar><el-treeref="inRef":data="inStudent"node-key="id" :filter-node-method="filterInNode"default-expand-all><template #default="{ node, data }"><slot :node="node" :data="data"><img :src="data.icon" style="height: 25px;width: 25px"><span style="margin: 0 10px;">{{ data.name }}</span></slot></template></el-tree></el-scrollbar></el-tab-pane></el-tabs></div>
</template>
const handleClick = (tab: TabsPaneContext) => {}
//外地过滤
const onFilterText = ref("")
watch(onFilterText, (val) => {onRef.value!.filter(val)
})
const filterOnNode = (value: string, data: Tree) => {if (!value) return truereturn data.name.includes(value);
}//内地过滤
const inFilterText = ref("")
watch(inFilterText, (val) => {inRef.value!.filter(val)
})
const filterInNode = (value: string, data: Tree) => {if (!value) return truereturn data.name.includes(value);
}//外地树
function addOnNode(data: any) {if(onRef.value){//是否有父节点,没有的话通过parentId拿到编制树的父节点if(data.parentId){const key = data.parentId;const node = onRef.value.getNode(key);if (node) {//通过父节点为编制树追加一个子节点const nodeParent = onRef.value.getNode(data.parentId);onRef.value.append(data,nodeParent);}}else{onRef.value.append(data,null);}}
}
//内地树
function addInNode(data: any) {if(inRef.value){//是否有父节点,没有的话通过parentId拿到编制树的父节点if(data.parentId){const key = data.parentId;const node = inRef.value.getNode(key);if (node) {//通过父节点为编制树追加一个子节点const nodeParent = inRef.value.getNode(data.parentId);inRef.value.append(data,nodeParent);}}else{inRef.value.append(data,null);}}
}//修改节点
function updateChildNode(data: any){if(onRef.value){const key = data.id;const node = onRef.value.getNode(key);if (node) {// 保留子节点数据const childrenBackup = node.data.children || [];// 合并数据Object.assign(node.data, {...data,children: childrenBackup});}}if(inRef.value){const key = data.id;const node = inRef.value.getNode(key);if (node) {// 保留子节点数据const childrenBackup = node.data.children || [];// 合并数据Object.assign(node.data, {...data,children: childrenBackup});}}
}//移除节点
function removeChildNode(data: any){if(onRef.value){onRef.value.remove(data);}if(inRef.value){inRef.value.remove(data);}
}defineExpose({addOnNode,addInNode,updateChildNode,removeChildNode
})