Appearance
经过一段时间的打磨,我们的响应式系统已初具雏形。接下来,加入 DOM 交互,"跑"起来看看效果。
轻点按钮,却发现 effect 像打了鸡血一样被反复触发——到底哪里出了问题?
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button id="btn">按钮</button>
<script type="module">
// import { ref, effect } from '../../../node_modules/vue/dist/vue.esm-browser.prod.js'
import {ref, effect} from '../dist/reactivity.esm.js'
const flag = ref(true)
effect(() => {
flag.value
console.count('effect')
})
btn.onclick = () => {
flag.value = !flag.value
}
</script>
</body>
</html>
问题原因
初始化阶段:首次运行 effect
,读取 flag.value
,触发 getter
收集依赖,诞生一条 link
,把 effect
和 flag
关联起来。
首次点击按钮:
flag.value
由true
改为false
,setter
被触发propagate
开始遍历依赖链表,找到link1
- 执行其中保存的
effect.run()
effect
再次运行,又读到flag.value
getter
再次被触发,又收集了一次依赖- 问题出现:同一次
effect.run()
里,第二条link2
被创建
结果如图所示:
第二次点击按钮:
propagate
发现了两条link
,于是effect.run()
被执行两次- 第二次点击后,
flag.value
又变回true
setter
触发,propagate
遍历链表- 发现两条
link
,于是执行两次effect.run()
- 每次
effect.run()
都会创建一条新的link
- 链表变成了三条、四条、五条……
结果如图所示:
关键问题
每次 effect
重新运行时,存在以下问题:
- 没有检查该 effect 是否已存在于当前依赖链表中
- 盲目地创建新的 link
- 导致同一个 effect 被多次收集,从而被多次触发
问题本质:依赖重复收集问题。每次按钮被触发时,链表上的每一个 link
都会触发 effect
的重新执行,而在每一次执行中,又创建新的 link
,形成恶性循环——link 数量呈指数级增长。
解决方案思路:在依赖收集时进行去重检查,检查当前 effect
是否已经存在于依赖链表中。如果存在,则跳过重复收集,确保每个 effect
在依赖链表中只出现一次。