漫客栈hd手机版
17.93MB · 2025-09-26
在解决了链表节点指数增长的问题后,我们还需要关注依赖的有效性。
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」,一起跟日安当同学。