建议先看完 ref响应式原理
先看一下reactive基本的使用
const obj = { name: 'loookooo' }
const state = reacitve(obj)
const fn = () => {
console.log('name: ', state.name)
}
effect(fn)
state.name = 'window'
这里拆出obj与fn为了方便下文的介绍。
obj经过reactive处理会返回响应式对象Proxy(会在reactiveMap中存储起来),这个Proxy在get时会进行依赖收集,set时会触发依赖,当然还有has,ownKeys,deleteProperty等的处理。细节可看 handlers API 解析。
当effect函数执行时,fn(副作用)首次执行,获取state.name时进行依赖收集,并把对应关系存储在targetMap(用来存放所有reactive对应依赖关系的WeakMap)中。
结构长这样: targetMap = { state:{ name:[ fn ] } }
当我们去改变state.name时,触发依赖,则会去targetMap中找到对应的副作用并遍历执行,如上面的[ fn ]就会被遍历执行。
有了基本的认识之后,我们根据例子再来细看每一步的操作。
可与reactive 相关API, effect 相关API一起看。
首先创建响应式对象
const state = reacitve(obj)
export const reactiveMap = new WeakMap<Target, any>() //用来存储经过reactive API转换过的target
export function reactive(target: object) {
...
return createReactiveObject(
target,
false,
mutableHandlers, //处理普通对象与数组
mutableCollectionHandlers, //处理集合结构如Map,WeakMap,Set,WeakSet
reactiveMap
)
}
调用 createReactiveObject 创建响应式对象并返回。
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
...
//如果转换过则直接返回,reactiveMap中查找
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
...
const proxy = new Proxy(
target,
//这里判断我们用的是 baseHandlers 因为我们传进来的是普通对象。
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
//存储转换后的对应关系
proxyMap.set(target, proxy)
return proxy
}
如果想看...可以翻读上面介绍的相关API。
关键句在 new Proxy(target, baseHandlers),baseHandlers即传进来的mutableHandlers。
//这里直接写上对应方法只是为了根据例子来方便介绍,源码中并不是这样,可以看上面介绍的相关API文章
export const mutableHandlers: ProxyHandler<object> = {
get(target, key, receiver){
...
//获取结果
const res = Reflect.get(target, key, receiver)
...
//依赖收集
track(target, TrackOpTypes.GET, key)
...
//如果为对象则进行响应式转换,所以不管对象嵌套多深,只有被用到的才会转换成响应式对象
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
},
set(target,key,value,receiver){
//获取旧值
let oldValue = (target as any)[key]
...
//判断是否key存在该对象上, state.name所以这里为true
const hadKey = ... hasOwn(target, key)
//获取结果
const result = Reflect.set(target, key, value, receiver)
if (target === toRaw(receiver)) {
if (!hadKey) {
//key不存在对象上,触发ADD类型依赖
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
//key存在对象上且新旧值有变更,触发SET类型依赖
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
},
//不作详细介绍
deleteProperty, // delete操作符
has, // name in state
ownKeys // Object.getOwnPropertyNames , Object.getOwnPropertySymbols
}
到这里 reactive(obj)响应式对象创建完毕,我们关注get与set方法,与vue2的Object.defineProperty对比,好处是不用再去递归进行响应式转换,且由于Proxy是对整个对象进行代理,在新增属性时,可自动变成响应式。
const fn = () => { console.log('name: ', state.name) }
effect(fn)
执行effect(fn), 此时trackOpBit = 1
在effect中,会根据fn创建ReactiveEffect对象,并执行其run方法。
run()
trackOpBit = 10
fn()
console.log('name: ', state.name)
track() //初始化targetMap中state.name对应的dep,此时 n = 0 , w = 0
trackEffects()
n = 10,shouldTrack = true
dep = [ ReactiveEffect ]
ReactiveEffect.deps = [ dep ]
n = 0
trackOpBit = 1
此时完成了依赖收集,并打印出'name: loookooo'.
targetMap = { state: { name: [ ReactiveEffect ] } }
state.name = 'window'
调用响应式对象set方法,触发tirgger,获取对应的dep,遍历执行副作用
即执行ReactiveEffect.run()
trackOpBit = 10
w = 10
fn()
console.log('name: ', state.name)
track()
trackEffects()
n = 10, shouldTrack = false
n = 0, w = 0
trackOpBit = 1
此时打印'name: window'