|
| 1 | +--- |
| 2 | +outline: deep |
| 3 | +layout: doc |
| 4 | +--- |
| 5 | +## 响应式原理 |
| 6 | +通过proxy拦截一个对象的读取,设置,当读取时将副作用函数存储到桶上,设置时将副作用从桶取出再执行 |
| 7 | + |
| 8 | +## 完善的响应式系统 |
| 9 | +### 提供一个注册副作用函数的函数 |
| 10 | +```js |
| 11 | +01 // 用一个全局变量存储被注册的副作用函数 |
| 12 | +02 let activeEffect |
| 13 | +03 // effect 函数用于注册副作用函数 |
| 14 | +04 function effect(fn) { |
| 15 | +05 // 当调用 effect 注册副作用函数时,将副作用函数 fn 赋值给activeEffect |
| 16 | +06 activeEffect = fn |
| 17 | +07 // 执行副作用函数 |
| 18 | +08 fn() |
| 19 | +09 } |
| 20 | +``` |
| 21 | +### 桶结构 |
| 22 | +桶是用来保存被操作字段与对应副作用函数的映射,最外层是weakmap,键名是代理对象target,键值是map,它的键名是target下的key,这个map的键值是一个set,用来保存副作用函数 |
| 23 | +### 分支切换 |
| 24 | +可能会产生遗留的副作用函数,解决方法是执行副作用之前,先从与他相关的依赖对应的副作用函数集合去除 |
| 25 | + |
| 26 | +当副作用函数执行完毕后,会重新建立联系,但在新的联系中不会包含遗留的副作用函数 |
| 27 | + |
| 28 | +也就需要明确知道哪些`依赖集合`中包含它(deps) |
| 29 | +```js |
| 30 | +01 function track(target, key) { |
| 31 | +02 // 没有 activeEffect,直接 return |
| 32 | +03 if (!activeEffect) return |
| 33 | +04 let depsMap = bucket.get(target) |
| 34 | +05 if (!depsMap) { |
| 35 | +06 bucket.set(target, (depsMap = new Map())) |
| 36 | +07 } |
| 37 | +08 let deps = depsMap.get(key) |
| 38 | +09 if (!deps) { |
| 39 | +10 depsMap.set(key, (deps = new Set())) |
| 40 | +11 } |
| 41 | +12 // 把当前激活的副作用函数添加到依赖集合 deps 中 |
| 42 | +13 deps.add(activeEffect) |
| 43 | +14 // deps 就是一个与当前副作用函数存在联系的依赖集合 |
| 44 | +15 // 将其添加到 activeEffect.deps 数组中 |
| 45 | +16 activeEffect.deps.push(deps) // 新增 |
| 46 | +17 } |
| 47 | +``` |
| 48 | + |
| 49 | +而调用副作用函数之前要清除 |
| 50 | +```js |
| 51 | +01 // 用一个全局变量存储被注册的副作用函数 |
| 52 | +02 let activeEffect |
| 53 | +03 function effect(fn) { |
| 54 | +04 const effectFn = () => { |
| 55 | +05 // 调用 cleanup 函数完成清除工作 |
| 56 | +06 cleanup(effectFn) // 新增 |
| 57 | +07 activeEffect = effectFn |
| 58 | +08 fn() |
| 59 | +09 } |
| 60 | +10 effectFn.deps = [] |
| 61 | +11 effectFn() |
| 62 | +12 } |
| 63 | +``` |
| 64 | +clean函数 |
| 65 | +```js |
| 66 | +01 function cleanup(effectFn) { |
| 67 | +02 // 遍历 effectFn.deps 数组 |
| 68 | +03 for (let i = 0; i < effectFn.deps.length; i++) { |
| 69 | +04 // deps 是依赖集合 |
| 70 | +05 const deps = effectFn.deps[i] |
| 71 | +06 // 将 effectFn 从依赖集合中移除 |
| 72 | +07 deps.delete(effectFn) |
| 73 | +08 } |
| 74 | +09 // 最后需要重置 effectFn.deps 数组 |
| 75 | +10 effectFn.deps.length = 0 |
| 76 | +11 } |
| 77 | +``` |
| 78 | +## 嵌套的effect |
| 79 | +例如 |
| 80 | +```js |
| 81 | +01 effect(() => { |
| 82 | +02 Foo.render() |
| 83 | +03 // 嵌套 |
| 84 | +04 effect(() => { |
| 85 | +05 Bar.render() |
| 86 | +06 }) |
| 87 | +07 }) |
| 88 | +``` |
| 89 | +所以需要一个effect栈 |
| 90 | +```js |
| 91 | +01 // 用一个全局变量存储当前激活的 effect 函数 |
| 92 | +02 let activeEffect |
| 93 | +03 // effect 栈 |
| 94 | +04 const effectStack = [] // 新增 |
| 95 | +05 |
| 96 | +06 function effect(fn) { |
| 97 | +07 const effectFn = () => { |
| 98 | +08 cleanup(effectFn) |
| 99 | +09 // 当调用 effect 注册副作用函数时,将副作用函数赋值给 activeEffect |
| 100 | +10 activeEffect = effectFn |
| 101 | +11 // 在调用副作用函数之前将当前副作用函数压入栈中 |
| 102 | +12 effectStack.push(effectFn) // 新增 |
| 103 | +13 fn() |
| 104 | +14 // 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并把 |
| 105 | +activeEffect 还原为之前的值 |
| 106 | +15 effectStack.pop() // 新增 |
| 107 | +16 activeEffect = effectStack[effectStack.length - 1] // 新增 |
| 108 | +17 } |
| 109 | +18 // activeEffect.deps 用来存储所有与该副作用函数相关的依赖集合 |
| 110 | +19 effectFn.deps = [] |
| 111 | +20 // 执行副作用函数 |
| 112 | +21 effectFn() |
| 113 | +22 } |
| 114 | +``` |
| 115 | +## 调度执行 |
| 116 | +有能力决定副作用函数执行的时机、次数以及方式 |
| 117 | + |
| 118 | +连续多次修改响应式数据但只会触发一次更新 |
| 119 | + |
| 120 | +这些任务同步加进去,同步执行完执行微任务 |
| 121 | +```js |
| 122 | +01 // 定义一个任务队列 |
| 123 | +02 const jobQueue = new Set() |
| 124 | +03 // 使用 Promise.resolve() 创建一个 promise 实例,我们用它将一个任务添加到微任务队列 |
| 125 | +04 const p = Promise.resolve() |
| 126 | +05 |
| 127 | +06 // 一个标志代表是否正在刷新队列 |
| 128 | +07 let isFlushing = false |
| 129 | +08 function flushJob() { |
| 130 | +09 // 如果队列正在刷新,则什么都不做 |
| 131 | +10 if (isFlushing) return |
| 132 | +11 // 设置为 true,代表正在刷新 |
| 133 | +12 isFlushing = true |
| 134 | +13 // 在微任务队列中刷新 jobQueue 队列 |
| 135 | +14 p.then(() => { |
| 136 | +15 jobQueue.forEach(job => job()) |
| 137 | +16 }).finally(() => { |
| 138 | +17 // 结束后重置 isFlushing |
| 139 | +18 isFlushing = false |
| 140 | +19 }) |
| 141 | +20 } |
| 142 | +21 |
| 143 | +22 |
| 144 | +23 effect(() => { |
| 145 | +24 console.log(obj.foo) |
| 146 | +25 }, { |
| 147 | +26 scheduler(fn) { |
| 148 | +27 // 每次调度时,将副作用函数添加到 jobQueue 队列中 |
| 149 | +28 jobQueue.add(fn) |
| 150 | +29 // 调用 flushJob 刷新队列 |
| 151 | +30 flushJob() |
| 152 | +31 } |
| 153 | +32 }) |
| 154 | +33 |
| 155 | +34 obj.foo++ |
| 156 | +35 obj.foo++ |
| 157 | +``` |
0 commit comments