computed & ref的基本用法以及实现 computed 的使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { reactive, effect, watch, watchEffect,computed} from '../../../node_modules/@vue/runtime-dom/dist/runtime-dom.esm-browser.js' const state = reactive ({firstname : 'g' , lastname : 'zy' })const fullname = computed ({ get ( ){ console .log ('getter' ) return state.firstname + state.lastname }, set (val ){ console .log (val) } }) console .log (fullname.value )console .log (fullname.value )console .log (fullname.value )
通过例子我肯可以看到,computed如下特性
计算属性主要是根据其他数据进行衍生数据的
计算属性默认是懒执行的,如果依赖的值不发生变化不会重新执行
计算属性的值自身无法修改
依赖的值变化了,后续再取值可以获取到新值
computed 的实现 computed可以传递一个函数,也可以传递一个对象 如果传递的是一个函数,这个函数就是getter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 export function computed (getterOrOptions ){ let getter; let setter; const isGetter = isFunction (getterOrOptions) if (isGetter){ getter = getterOrOptions setter = () => { console .log ('warn' ) } }else { getter = getterOrOptions.get setter = getterOrOptions.set } }
computed 返回的事一个普通对象,为了方便依赖收集,应该把普通值包装成一个对象 类似这样的结构
1 2 3 4 5 6 7 8 9 10 11 const abc = 123 let obj = { _value : abc, get value (){ return this ._value }, set value (xxx ){ this ._value = xxx } }
vue中是通过 ComputedRefImpl 类来实现的
这个类接受getter setter两个函数
这个类主要是用来收集依赖,离不开 ReactiveEffect
getter就相当于effect传递的函数,调用run方法内部就会进行依赖收集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import { isFunction } from "@vue/shared" ;import { ReactiveEffect } from "./effect" ;class ComputedRefImpl { public effect constructor (getter,setter ){ this .effect = new ReactiveEffect (getter) } get value (){ this ._value = this .effect .run () return this ._value } set value (newVal ){ this .setter (newVal) } } export function computed (getterOrOptions ){ return new ComputedRefImpl (getter,setter) }
这时候控制台打印 (还没有具备缓存功能)1 2 3 4 5 6 getter gzy getter gzy getter gzy
增加缓存功能
增加一个 _dirty 来标识是否缓存
在取值前根据 _dirty 判断是否需要重新执行 run 方法
在每次设置值(值发生了改变)后重置 _dirty 状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class ComputedRefImpl { public effect public _value public _dirty = true public dep = new Set () constructor (getter,setter ){ this .effect = new ReactiveEffect (getter, ()=> { if (!this ._dirty ){ this ._dirty = true } }) } get value (){ if (this ._dirty ){ this ._value = this .effect .run () this ._dirty = false } return this ._value } set value (newVal ){ this .setter (newVal) } }
计算属性应该也进行依赖收集 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import { reactive, effect, watch, watchEffect ,computed} from './reactivity.js' const state = reactive ({firstname : 'g' , lastname : 'zy' })const fullname = computed ({ get ( ){ console .log ('getter' ) return state.firstname + state.lastname }, set (val ){ console .log (val) } }) effect (()=> { app.innerHTML = fullname.value }) state.firstname = 'hello'
这时候firstname发生了变化,但是页面并没有重新渲染
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 27 28 29 30 31 32 33 class ComputedRefImpl { public effect public _value public _dirty = true public dep = new Set () constructor (getter, setter ) { this .effect = new ReactiveEffect (getter, () => { if (!this ._dirty ) { this ._dirty = true triggerEffect (this .dep ) } }) } get value () { trackEffect (this .dep ) if (this ._dirty ) { this ._value = this .effect .run () this ._dirty = false } return this ._value } set value (newVal ) { this .setter (newVal) } }
可以基于以前的 track 和 trigger 进行拆分
effect.ts 中 track 方法拆分
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 27 28 29 30 31 32 33 34 35 36 37 const targetMap = new WeakMap ()export function track (target,key ){ if (activeEffect){ let depsMap = targetMap.get (target) if (!depsMap){ targetMap.set (target,(depsMap = new Map )) } let dep = depsMap.get (key) if (!dep){ depsMap.set (key, (dep = new Set ())) } trackEffect (dep) } } export function trackEffect (dep ){ let shouldTrack = !dep.has (activeEffect) if (shouldTrack){ dep.add (activeEffect) activeEffect.deps .push (dep) } }
effect.ts 中 trigger 方法拆分
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 27 28 29 export function trigger (target,key,newVal,oldVal ){ const depsMap = targetMap.get (target) if (!depsMap) { return } const dep = depsMap.get (key); triggerEffect (dep) } export function triggerEffect (dep ){ const effects = [...dep] effects && effects.forEach (effect => { if (effect !== activeEffect){ if (effect.scheduler ){ effect.scheduler () }else { effect.run () } } }) }
直接访问fallname 现在访问fallname需要 fallname.value ,修改代码直接fallname取值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const fullname = computed ({ get ( ) { console .log ('getter' ) return state.firstname + state.lastname }, set (val ) { console .log (val) } }) const state = reactive ({ firstname : 'g' , lastname : 'zy' , fullname })effect (() => { app.innerHTML = state.fullname }) setTimeout (() => { state.firstname = 'hello' }, 2000 )
增加 public __v_isRef = true
1 2 3 4 5 6 7 class ComputedRefImpl { public effect public _value public _dirty = true public dep = new Set () public __v_isRef = true
handler.ts
如果存在 __v_isRef 直接返回 value
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 27 28 29 30 export const mutableHandlers = { get (target,key,receiver ){ if (key === ReactiveFlags .IS_REACTIVE ){ return true } if (isRef (target[key])){ return target[key].value } if (isObject (target[key])){ return reactive (target[key]) } const res = Reflect .get (target,key ,receiver) track (target,key) return res } } function isRef (value ){ return !!(value && value.__v_isRef ) }
ref 的使用 1 2 3 4 5 6 7 8 9 10 11 12 import { reactive, effect, watch, watchEffect, computed, ref } from '../../../node_modules/@vue/runtime-dom/dist/runtime-dom.esm-browser.js' let flag = ref (true ) effect (() => { app.innerHTML = flag.value }) setTimeout (() => { flag.value = false }, 1000 )
ref 的实现 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 27 28 import { isObject } from "@vue/shared" import { reactive } from "./reactivity" import { trackEffect, triggerEffect } from "./effect" export function toReactive (value ){ return isObject (value) ? reactive (value): value } class RefImpl { public _value constructor (public rawValue ){ this ._value = toReactive (rawValue) } get value (){ return this ._value } set value (newVal ){ if (newVal !== this .rawValue ){ this .rawValue = newVal this ._value = toReactive (newVal) } } } export function ref (value ){ return new RefImpl (value) }
增加依赖收集,标识 ref
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class RefImpl { public _value public dep = new Set () public __v_isRef = true constructor (public rawValue ){ this ._value = toReactive (rawValue) } get value (){ trackEffect (this .dep ) return this ._value } set value (newVal ){ if (newVal !== this .rawValue ){ this .rawValue = newVal this ._value = toReactive (newVal) triggerEffect (this .dep ) } } }