一、BUG描述
情况: 点击右侧字段列, 隐藏 “创建日期和更新日期”,再显示 “创建日期和更新日期”,操作栏的显示就错了。如果多点击几次,操作栏显示会恢复正常,且之后一直正常。
技术栈: vue3 + element-plus
二、element-plus的Table组件的封装代码
const defaultSlot = useSlots().default?.()
let canLookSlot = defaultSlot.filter(xxx)
// 拿到默认插槽,并通过filter方法过滤需要的数据,通过函数式组件渲染canLookSlot。
三、排查步骤
3.1 打印 “点击前后” 的slot数据
const vNodeList = computed(() => {
const result = defaultSlot.filter((item: any, index:number) => {...})
console.log('点击后数据:', defaultSlot, result)
debugger
return result
})
3.2 观察数据, 得出观点
a. debugger断点时,数据是正常的
b. 页面渲染后,数据是异常的
c. 观点: 在vue渲染时,Diff时应该有一些问题
3.3 让Vue3源码报错
a. 渲染后,slot数据中item.children.default被改了,就需要看vue源码是如何改的,将item.children.default设置为不可写,直接让源码报错
const vNodeList = computed(() => {
const result = defaultSlot.filter((item: any, index:number) => {
// 添加这段代码
Object.defineProperty(item.children, 'default', {
value: item.$default, // 初始值
writable: false, // 不可写
enumerable: true, // 可枚举
configurable: false // 不可配置
})
})
return result
})
b. 浏览器报错
3.4 调试源码
a. 依次把这些堆栈位置打上断点
b. 再加一个断点在业务文件,当业务文件修改数据后,再启动vue文件的断点。
PS: 因为渲染的时候,vue的断点会一直多次触发。
c. 经排查, 重新渲染diff时,“新的DOM” 把 “操作栏的DOM位置” 替换了,并重置了slot数据(操作栏的children.default函数)。
3.4 修复
a. 观察源码执行过程中, 有一个方法isSameVNodeType,内部判断了key,是否同一个元素
b. 为所有插槽数据添加随机key
let defaultSlot = useSlots().default?.().map((item) => {
item.props.key = randomString(8) + randomNumber(8)
})
3.5 修复结果
3.6 其它修复方式
待深入排查有无其它解决方案,当前方式感觉并不优