Skip to content

经过一段时间的打磨,我们的响应式系统已初具雏形。接下来,加入 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,把 effectflag 关联起来。

首次点击按钮

  • flag.valuetrue 改为 falsesetter 被触发
  • 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 重新运行时,存在以下问题:

  1. 没有检查该 effect 是否已存在于当前依赖链表中
  2. 盲目地创建新的 link
  3. 导致同一个 effect 被多次收集,从而被多次触发

问题本质依赖重复收集问题。每次按钮被触发时,链表上的每一个 link 都会触发 effect 的重新执行,而在每一次执行中,又创建新的 link,形成恶性循环——link 数量呈指数级增长

解决方案思路:在依赖收集时进行去重检查,检查当前 effect 是否已经存在于依赖链表中。如果存在,则跳过重复收集,确保每个 effect 在依赖链表中只出现一次。

最后更新时间: