effect是用来注册副作用函数,同时它允许指定一些选项参数options,例如指定scheduler调度器控制副作用函数的执行时机和方式,先实现一个懒执行的副作用函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function effect(fn,options = {}) { const effectFn = () => { cleanup(effectFn) activeEffect = effectFn effectStack.push(effectFn) fn() effectStack.pop() activeEffect = effectStack[effectStack.length-1] } effectFn.options = options effectFn.deps = [] if(!options.lazy) { effectFn() } return effectFn }
|
实现了让副作用函数不立即执行的功能,将副作用函数effectFn作为effect函数的返回值,这就意味着当调用effect函数时,通过其返回值能够拿到对应的副作用函数,这样我们能够手动执行副作用函数
1 2 3 4 5
| const effectFn = effect(()=>{ console.log(obj.foo) },{lazy:true})
effectFn()
|
如果把传递给effect的函数看做一个getter,那么这个getter函数可以返回任何值
1 2 3 4 5
| const effectFn = effect(
() => obj.foo + obj.bar, { lazy: true} )
|
为了手动执行副作用函数时就能拿到其返回值,改动effect函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function effect(fn,options = {}){ const effectFn = () => { cleanup(effectFn) activeEffect = effectFn effectStack.push(effectFn) const res = fn() effectStack.pop() activeEffect = effectStack[effectStack.length-1] return res; } effectFn.options = options effectFn.deps = [] if(!options.lazy){ effectFn() } return effectFn }
|
传递给effect函数的参数fn才是真正的副作用函数,而effectFn是我们包装后的副作用函数,为了通过effectFn得到真正的副作用函数fn的执行结果,我们需要将其保存到res变量,然后将其作为effectFn函数的返回值
接下来就可以定义cmputed函数
1 2 3 4 5 6 7 8 9 10 11 12 13
| function computed(getter) { const effectFn = effect(getter,{ lazy: true }) const obj = { get value() { return effectFn() } } return obj }
|
computed函数接收一个getter函数作为参数,我们把getter函数作为副作用函数,用它创建一个lazy的effect,computed函数的执行会返回一个对象,该对象的value属性是一个访问器属性,只有当读取value值时,才执行effectFn并将结果作为返回值返回
使用computed函数创建一个计算属性:
1 2 3 4
| const data ={ foo: 1, bar: 2} const obj = new Proxy(data, {...}) const sumRes = computed(() => obj.foo + obj.bar) console.log(sumRes.value)
|
当多次访问sumRes.value的值,会导致effectFn进行多次计算,即使obj.foo和obj,bar的值本身没有变化,利用闭包实现对值进行缓存的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function computed(getter) { let value let dirty = true const effectFn = effect(getter,{ lazy:true }) const obj = { get value(){ if(dirty){ value = effectFn() dirty = false } return value } } return obj }
|
但是此时改变obj.foo或者obj.bar,再访问sumRes.value会发现访问到的值没有发生变化
1 2 3 4 5 6 7 8
| const data = {foo: 1,bar: 2} const obj = new Proxy(data,{...}) const sumRes = computed(() => obj.foo+obj.bar) console.log(sumRes.value) console.log(sumRes.value) obj.foo++;
console.log(sumRes.value)
|
这是因为,第一次访问sumRes.value的值后,变量dirty被设置为false,代表不需要计算,即使我们修改obj.foo的值,但只要dirty的值为false,就不会重新计算,所有导致我们得到错误的值,因此当obj.foo或者obj.bar的值发生改变时,只要dirty的值重置为true就可以,这时就用到scheduler选项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| function computed(getter) { let value let dirty = true cont effectFn = effect(getter, { lazy: true, scheduler() { if(!dirty) { dirty = true trigger(obj,'value') } } }) const obj = { get value() { if(dirty){ value = effectFn() dirty = false } track(obj,'value') return value } } return obj }
|
当读取一个计算属性value,需要手动调用track函数,把计算属性返回的对象obj作为target,同时作为第一个参数传递给track函数,当计算属性所依赖的响应式数据变化时会立即调度函数,在调度函数内手动调用trigger函数触发响应即可。