Vue响应式之依赖收集-vue3
前言
上篇写了Vue2 的依赖收集和依赖更新的大致逻辑,这篇开始看Vue3的依赖收集,Vue3写法上兼容Vue2,为了快速了解核心流程,这里只看setup写法的逻辑。
vue3这篇相较于vue2的会简略很多,一是2中已经理清了依赖收集和更新的逻辑,所以这篇只关心依赖收集相关的,篇幅会较短,二是到现在还未细看过3的源码,也会忽略一些细节。
正文
Vue3源码版本为3.2.37
setup 写法中响应式数据通常以reactive 或 ref 定义,依赖收集逻辑就以 reactive
为入口来看
export function reactive(target: object) {
// ...
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
// ...
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
直入响应式核心 - proxy,reactive方法经 createReactiveObject
处理返回一个 Proxy
,proxy
的 handle 为 collectionHandlers
或者 baseHandlers
,前者为 Map、Set、WeakMap、WeakSet 数据结构的 handle,这里只看 baseHandlers
export const mutableHandlers = {
get: createGetter(),
set: createSetter(),
deleteProperty,
has,
ownKeys
}
baseHandlers 中定义了五类操作时的代理
依赖收集的原理都是一样的,不管是通过 Object.defineProperty
还是 Proxy
,都是在 getter 中收集依赖,在 setter 中通知更新
createGetter
用来创建 getter
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
// ...
const res = Reflect.get(target, key, receiver)
track(target, TrackOpTypes.GET, key)
//...
return res
}
}
track 是收集的核心逻辑,类似 Vue2 中一系列 Dep
操作
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (shouldTrack && 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 = createDep()))
}
const eventInfo = { effect: activeEffect, target, type, key }
trackEffects(dep, eventInfo)
}
}
targetMap
是一个 WeakMap 缓存区,记录所有的响应对象,防止被重复代理,键就是对应的响应式对象(这里使用 weakmap 的好处是它的键是个弱引用,不会一直持有 target 的引用导致内存泄漏),effect
记录着的是当前调用这个proxy
的函数,类似 Vue2 中的 Dep.target
。
depsMap 的作用就是 Vue2中的 dep.subs,不同的2中的数组,Vue3中是个 Set,由 createDep
创建,记录响应式对象 key 的所有依赖函数
到这里能看出来,抛开了IE兼容包袱的Vue3 运用各种es6 的标准,单从这些代码层面优化也能看到性能也会优于Vue2.
export function trackEffects(dep) {
dep.add(activeEffect!)
// ...
}
搞清楚 activeEffect 如何更改指向,何时更改,就完成了整个依赖的收集过程
export class ReactiveEffect<T = any> {
constructor(fn, cheduler, scope) {
this.fn = fn
// ..
recordEffectScope(this, scope)
}
run() {
activeEffect = this
return this.fn()
// ...
}
}
activeEffect 指向由 ReactiveEffect 实例 run 方法修改。ReactiveEffect 的作用和Vue2 中的 Watcher 作用类似,最终还是在 mount阶段通过 mountComponent 实例化
const mountComponent = (
initialVNode,
...
) => {
// ...
setupRenderEffect(
instance,
...
)
}
const setupRenderEffect = (
instance,
...
) => {
//
// ...
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
() => queueJob(update),
instance.scope // track it in component's effect scope
))
const update = (instance.update = () => effect.run())
// ...
update()
}
componentUpdateFn 就是组件的更新函数,会创建 vnode,并进行patch 最终渲染dom
以上就是整个 get 过程,响应式数据定义和代理脱离组件,通过暴露 ReactiveEffect.run 允许修改 activeEffect 指向,组件 mount 阶段实例化 effect,将响应式数据与组件关联,并将 instance.update 指向 effect.run
依赖更新的核心逻辑和2中是一样的,代理 setter ,遍历 dep 调用 effect.run
function createSetter(shallow = false) {
return function set(target, key, value,receiver) {
// ...
const result = Reflect.set(target, key, value, receiver)
// ...
trigger(target, TriggerOpTypes.ADD, key, value)
return result
}
}
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
let deps = []
deps = [...depsMap.values()]
const eventInfo = target, type, key, newValue, oldValue, oldTarget }
const effects []
for (const dep of deps) {
effects.push(...dep)
}
triggerEffects(createDep(effects), eventInfo)
}
}
export function triggerEffects(dep) {
for (const effect of effects) {
// ...
effect.run()
}
}