smartskip智能跳绳软件
15.27MB · 2025-12-21
在解决了链表节点指数增长的问题后,我们还需要关注依赖的有效性。
effect 的执行路径可能因为条件判断或程序逻辑不同而改变,导致某些依赖在本次执行中已经不再需要。
如果这些“过期依赖”没有被清理:
effect 虽然已经不依赖某个 ref,但这个 ref 的变化仍然会触发 effect。因此,在收集依赖时,不只要能正确复用,也必须具备 清理过期依赖 的机制。
下面我们来说明需要清理依赖的两种状况。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
<style>
body {
padding: 150px;
}
</style>
</head>
<body>
<div id="app"></div>
<button id="flagBtn">update flag</button>
<button id="nameBtn">update name</button>
<button id="ageBtn">update age</button>
<script type="module">
// import { ref, effect } from '../../../node_modules/vue/dist/vue.esm-browser.js'
import { ref, effect } from '../dist/reactivity.esm.js'
const flag = ref(true)
const name = ref('姓名')
const age = ref(18)
effect(() => {
console.count('effect')
if(flag.value){
app.innerHTML = name.value
} else {
app.innerHTML = age.value
}
})
flagBtn.onclick = () => {
flag.value = !flag.value
}
nameBtn.onclick = () => {
name.value = '姓名' + Math.random()
}
ageBtn.onclick = () => {
age.value++
}
</script>
</body>
</html>
我们现在有三个变量,flag、name、age。预期行为是:
flag 是 true,那么点击 name 按钮会触发更新,但是点击 age 按钮不会触发更新。flag 是 false,那么点击 age 按钮会触发更新,但是点击 name 按钮不会触发更新。这很合理,因为在某个分支下未被依赖的变量,其变化不应该触发更新。
effect: 1,此时 flag 是 true。update flag:effect 更新,输出 effect: 2,此时 flag 变为 false。update name:我们期望它没有反应,因为“如果 flag 是 false,点击 name 不会触发更新。”effect 仍然被更新,console 输出了 effect: 3。我们先来看一下 effect 以及 name 内部的内容。执行步骤是初始化后先点击 update flag,再点击 update name。
const e = effect(() => {
console.count('effect')
if(flag.value){
app.innerHTML = name.value
} else {
app.innerHTML = age.value
}
})
//...
nameBtn.onclick = () => {
name.value = '姓名' + Math.random()
console.dir(e.effect) // 访问挂载在 runner 上的 effect 实例
console.log(name)
}
我们预期
effect 内部的依赖链表 (deps) 中,不会有 name 节点:实际上是正确的,当前 Link 节点是 flag → age。
我们预期
name 的订阅者链表 (subs) 中,不应该有 effect 的记录,但实际上输出结果显示它仍处于被订阅的状态。
我们现在了解到异常情况是:
effect 内部的依赖链表上只有 flag 和 age,没有 name,这是我们想要的正确结果。name 内部的订阅者链表上却仍然记录着这个 effect,这层关系应该被清除。
遇到
flag,收集依赖,创建链表节点。
遇到
name,创建链表节点。此时依赖关系是:effect 依赖 flag 和 name。
点击按钮,
effect 重新执行,run 函数让 depsTail 指向 undefined,进入“重新收集”状态。
接着
link 函数检查 flag,发现可以复用 Link1。
depsTail 指向 Link1,此时 flag 的值是 false。
由于
flag 是 false,程序进入 else 分支,遇到 age。之前没有收集过 age 的依赖,于是创建新的链表节点 Link3。
创建
age 的链表节点之后,effect 的 deps 链表更新,depsTail 指向新节点 Link3。
问题来了:此时请看黄底色的地方,你会发现
effect 的 deps 链表上已经没有 Link2 (连接 name 的节点) 了,但是 Link2 的 sub 属性仍然指向这个 effect。同时,name 的 subs 链表也依然保留着 Link2。
所以才会出现 name 更新时,仍然会触发 effect 执行的情况。
const flag = ref(true)
const name = ref('姓名')
const age = ref(18)
let count = 0
effect(() => {
console.count('effect')
if(count > 0) return
count++
if(flag.value){
app.innerHTML = name.value
} else {
app.innerHTML = age.value
}
})
// ... (按钮点击事件同上)
到时修正时会一并处理,所以我们先来说明第二种需要清除依赖的状况。
预期:effect 只会触发两次。(因为 count > 0 会返回,之后无论如何点击按钮都不应再触发)
此时 count 是 0,继续往下执行。
count++,count 现在是 1。flag.value → 收集依赖 flag。flag 是 true,接着读到 name.value → 收集依赖 name。name 按钮:
effect 重新 run()。depsTail 设为 undefined。effect 函数体,马上遇到 if (count > 0) return 而被中断,因此本次执行没有进行任何依赖收集。遇到 return 程序应该会中断,可是你一直点击 name 按钮,console.count('effect') 仍然一直触发更新。
当遇到
if (count > 0) return 时,effect 没有访问任何依赖,其 deps 链表上应该要是空的。可是,链表上面有之前建立的依赖关系没有被清除。
此时 effect 的 deps 链表状态一直处于:有头节点 deps,并且尾节点 depsTail = undefined。
明天我们就会来谈如何清除这些过期的依赖。
想了解更多 Vue 的相关知识,抖音、B站搜索我师父「远方os」,一起跟日安当同学。
15.27MB · 2025-12-21
40.05MB · 2025-12-21
50.79MB · 2025-12-21