往期内容:
《Vue进阶教程》第三课:Vue响应式原理
《Vue进阶教程》第四课:reactive()函数详解
《Vue进阶教程》第五课:ref()函数详解(重点)
《Vue进阶教程》第六课:computed()函数详解(上)
《Vue进阶教程》第七课:computed()函数详解(下)
《Vue进阶教程》第八课:watch()函数的基本使用
《Vue进阶教程》第九课:watch()函数的高级使用
《Vue进阶教程》第十课:其它函数
《Vue进阶教程》第十一课:响应式系统介绍
《Vue进阶教程》第十二课:实现一对多
1) 为什么要依赖收集
前面, 我们并没有区分不同属性对应的副作用函数, 而是全部放入到副作用桶
里.
示例
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><script>// 定义一个副作用桶bucketconst bucket = new Set() // 修改/*** 定义响应式* @param [object] : 普通对象* @return [Proxy] : 代理对象*/function reactive(data) {// 如果传入的data不是一个普通对象, 不处理if (typeof data !== 'object' || data == null) returnreturn new Proxy(data, {get(target, key) {// console.log(`自定义访问${key}`)return target[key]},set(target, key, value) {// console.log(`自定义设置${key}=${value}`)target[key] = value // 先更新值bucket.forEach((fn) => fn())return true},})}const pState = reactive({ name: 'hello', age: 20 })function effectName() {console.log('effectName...', pState.name)}bucket.add(effectName)function effectAge() {console.log('effectAge...', pState.age)}bucket.add(effectAge)setTimeout(() => {pState.name = 'brojie'}, 1000)</script></body>
</html>
接下来我们思考这样的问题
🤔思考
如果一个副作用函数effectName只引用了name
另一个副作用函数effectAge只引用了age
理论上, 更新name只需要重新执行effectName而不需要重新执行effectAge
换句话说, 依赖收集就是建立属性与副作用函数的对应关系
2) 实现思路
1将当前副作用函数保存到一个全局变量
2当执行副作用函数时, 会触发代理对象的自定义get操作
3在get操作时, 将全局变量中保存的函数添加到副作用桶
3) 具体实现
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><script>// 定义一个副作用桶bucketconst bucket = new Set() // 修改// 定义一个全局变量, 作于保存 `当前副作用函数`let activeEffect = null/*** 定义响应式* @param [object] : 普通对象* @return [Proxy] : 代理对象*/function reactive(data) {// 如果传入的data不是一个普通对象, 不处理if (typeof data !== 'object' || data == null) returnreturn new Proxy(data, {get(target, key) {// console.log(`自定义访问${key}`)if (activeEffect != null) {bucket.add(activeEffect)}return target[key]},set(target, key, value) {// console.log(`自定义设置${key}=${value}`)target[key] = value // 先更新值bucket.forEach((fn) => fn())return true},})}const pState = reactive({ name: 'hello', age: 20 })// 定义副作用函数function effectName() {console.log('effectName...', pState.name)}// 将副作用函数保存到全局变量中activeEffect = effectName// 执行副作用函数effectName()// 重置全局变量activeEffect = nullsetTimeout(() => {pState.name = 'brojie'}, 1000)</script></body>
</html>
4) 优化
接下来, 我们优化一下, 封装一个注册函数
, 方便注册副作用函数
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><script>// 定义一个副作用桶bucketconst bucket = new Set() // 修改// 定义一个全局变量, 作于保存 `当前副作用函数`let activeEffect = null/*** 定义响应式* @param [object] : 普通对象* @return [Proxy] : 代理对象*/function reactive(data) {// 如果传入的data不是一个普通对象, 不处理if (typeof data !== 'object' || data == null) returnreturn new Proxy(data, {get(target, key) {// console.log(`自定义访问${key}`)if (activeEffect != null) {bucket.add(activeEffect)}return target[key]},set(target, key, value) {// console.log(`自定义设置${key}=${value}`)target[key] = value // 先更新值bucket.forEach((fn) => fn())return true},})}/*** 注册副作用函数* @params [function]: 要注册的 副作用函数*/function registEffect(fn) {if (typeof fn !== 'function') return// 将当前注册的副作用函数 保存 到全局变量中activeEffect = fn// 执行当前副作用函数, 收集依赖fn()// 重置全局变量activeEffect = null}const pState = reactive({ name: 'hello', age: 20 })registEffect(function effectName() {console.log('effectName...', pState.name)})setTimeout(() => {pState.name = 'brojie'}, 1000)</script></body>
</html>
提示
源码里注册副作用函数的函数名就叫effect
这里我们重在理解函数的功能, 不用去纠结名字