> 文章列表 > Vue3源码梳理:基于reactive和effect函数来看响应性系统的核心实现

Vue3源码梳理:基于reactive和effect函数来看响应性系统的核心实现

Vue3源码梳理:基于reactive和effect函数来看响应性系统的核心实现

阅读源码原则

  • 简化和调试,掌握核心流程
  • 不被过多细节束缚,只专注于主线

reactive 和 effect 方法的源码实现

1 )用户代码 示例,业务代码如下:

<script src='../../dist/vue.global.js'></script><body><div id='app'></div>
</body><script>const { reactive, effect } = Vueconst obj = reactive({name: '张三'})effect(()=>{document.querySelector('#app').innerText = obj.name // 注意,这里是 getter 行为})const timer = setTimeout(() => {clearTimeout(timer)obj.name = '李四' // 这里是 setter 行为}, 2000)
</script>

在上述代码中,我们在 reactive 中传入了一个 对象 构建了一个 obj 对象,在 effect 中 传入了一个回调函数

我们想要从用户示例代码中,追根溯源,看下 reactive 和 effect 内部 到底是怎么执行的

2 )进行reactive的debug

  • 现在chrome的Sources面板的Pages子面板找到 reactive.ts 中,找到 reactive() 方法,打上一个断点

  • 刷新页面,走到了断点处, 程序是这样走的:

    export function reactive(target: object) {// if trying to observe a readonly proxy, return the readonly version.// 这里跳过if (isReadonly(target)) {return target}// 直接走到这里return createReactiveObject(target,false,mutableHandlers,mutableCollectionHandlers,reactiveMap)
    }
    
  • 代码进入到 createReactiveObject 方法

    function createReactiveObject(target: Target,isReadonly: boolean,baseHandlers: ProxyHandler<any>,collectionHandlers: ProxyHandler<any>,proxyMap: WeakMap<Target, any>
    ) {// 这里会跳过if (!isObject(target)) {if (__DEV__) {console.warn(`value cannot be made reactive: ${String(target)}`)}return target}// target is already a Proxy, return it.// exception: calling readonly() on a reactive object// 这里匹配不到会跳过if (target[ReactiveFlags.RAW] &&!(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {return target}// target already has corresponding Proxy// 这里从 proxyMap 中读取一个实例,这里 proxyMap 对应一个 WeakMap, 我们暂时把它当成一个普通的map对象const existingProxy = proxyMap.get(target)// 这里读取不到,也会跳过if (existingProxy) {return existingProxy}// only specific value types can be observed.// 这里尝试读取 targetType, 目前值是 1const targetType = getTargetType(target)// 这里不匹配也可以直接跳过if (targetType === TargetType.INVALID) {return target}// 这里是重点:创建一个代理对象const proxy = new Proxy(target,targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers // 主要看这里的三目运算, 判断当前 targetType 是否是指定值)// 这里将target存到map中建立一个对应关系proxyMap.set(target, proxy)return proxy // 最终return 这个 proxy 对象
    }
    
  • 其中上述说的 targetType 是一个枚举对象

    const enum TargetType {INVALID = 0,COMMON = 1,COLLECTION = 2
    }
    
  • 我们跳到 baseHandlers 方法中,看发生了什么, 往上查找代码,它是 createReactiveObject 的第三个参数

    • 也就是 reactive 方法 中 return createReactiveObject 的 第三个参数:mutableHandlers
    • 而 mutableHandlers 是 baseHandlers.ts 中定义的
      export const mutableHandlers: ProxyHandler<object> = {get,set,deleteProperty,has,ownKeys
      }
      
    • 在上述代码中有两个关键的属性,get和set,这里我们要考虑到 Proxy 中的 getter 和 setter 行为被触发的时机
    • 我们需要注意,我们自己的业务代码中的 getter 和 setter, 一旦触发了它们,下面两个函数就会被执行
      • 先来看下 get
        const get = /*#__PURE__*/ createGetter()
        
      • 这个 get 是一个 createGetter 的返回,return 了一个 get函数, 就是上述Handlers中的get
        function createGetter(isReadonly = false, shallow = false) {return function get(target: Target, key: string | symbol, receiver: object) {// 下面一些列的判断都会被跳过if (key === ReactiveFlags.IS_REACTIVE) {return !isReadonly} else if (key === ReactiveFlags.IS_READONLY) {return isReadonly} else if (key === ReactiveFlags.IS_SHALLOW) {return shallow} else if (key === ReactiveFlags.RAW &&receiver ===(isReadonly? shallow? shallowReadonlyMap: readonlyMap: shallow? shallowReactiveMap: reactiveMap).get(target)) {return target}// 这里不匹配也会被跳过const targetIsArray = isArray(target)// 这里不匹配也会被跳过if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {return Reflect.get(arrayInstrumentations, key, receiver)}// 这里 Reflect.get 返回的是 receiver 的 keyconst res = Reflect.get(target, key, receiver)// 这里不匹配也会被跳过if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {return res}// 这里会执行,注意下这里的 track 函数,主要为了 依赖收集和依赖触发// 这里的 track 是一个依赖收集的过程if (!isReadonly) {track(target, TrackOpTypes.GET, key)}if (shallow) {return res}if (isRef(res)) {// ref unwrapping - skip unwrap for Array + integer key.return targetIsArray && isIntegerKey(key) ? res : res.value}if (isObject(res)) {// Convert returned value into a proxy as well. we do the isObject check// here to avoid invalid value warning. Also need to lazy access readonly// and reactive here to avoid circular dependency.return isReadonly ? readonly(res) : reactive(res)}// 最后这里会执行,返回 res 结果return res}
        }
        
  • 我们看下上述 getter 中的 track 发生了什么, 在 effect.ts 中

    export function track(target: object, type: TrackOpTypes, key: unknown) {// 注意这里,activeEffect 在 ReactiveEffect类 run函数里进行的赋值;shouldTrack 也被赋值为 true,所以这里会执行if (shouldTrack && activeEffect) {// 这里的 targetMap 也是一个 WeekMap, 这里的get 是 undefinedlet depsMap = targetMap.get(target)// 因为是 undefined,所以,这里会执行,对 targetMap 进行一个赋值操作if (!depsMap) {targetMap.set(target, (depsMap = new Map())) // 这时候,这里的 targetMap 就有值了,以 target 这个对象为key, 一个新Map对象作为value}// 这里,同上,也是取不出来值的,这里同样是 undefinedlet dep = depsMap.get(key)// 这里会执行if (!dep) {// 此时将key作为key, 将createDep()作为value存储进depsMap这个map中, 这里的 dep 本质上是一个set, 参考 createDep 源码实现depsMap.set(key, (dep = createDep()))}const eventInfo = __DEV__? { effect: activeEffect, target, type, key }: undefined// 这里注意,trackEffects(dep, eventInfo)}
    }
    
  • 通过以上代码,我们可知 targetMap 这个WeekMap还是比较复杂的,其结构说明如下:

    • targetMap
      • key: target
      • value: Map
        • key: key
        • value: Set
  • 我们再看看,上述的 createDep() 发生了什么, 在 dep.ts 中

    export const createDep = (effects?: ReactiveEffect[]): Dep => {// 这里是一个set集合const dep = new Set<ReactiveEffect>(effects) as Depdep.w = 0dep.n = 0return dep
    }
    
  • 我们再看下 trackEffects 发生了什么

    export function trackEffects(dep: Dep,debuggerEventExtraInfo?: DebuggerEventExtraInfo
    ) {let shouldTrack = falseif (effectTrackDepth <= maxMarkerBits) {if (!newTracked(dep)) {dep.n |= trackOpBit // set newly trackedshouldTrack = !wasTracked(dep)}} else {// Full cleanup mode.shouldTrack = !dep.has(activeEffect!)}// 关注下这里if (shouldTrack) {// 这里的dep, 本质是一个 Set, 这里将 effect 实例添加进这个Set中保存// 下面两行建立了  dep 和 activeEffect 之间的联系,这样我们就可以通过被代理对象的key获取到当前的 activeEffect, 也就是 ReactiveEffect实例,这里我们完成了依赖收集dep.add(activeEffect!)activeEffect!.deps.push(dep)// 由上面两步,我们的 targetMap 对象中就保存了 ReactiveEffect 实例if (__DEV__ && activeEffect!.onTrack) {activeEffect!.onTrack({effect: activeEffect!,...debuggerEventExtraInfo!})}}
    }
    
  • 由上可知,依赖收集的过程

    • 本质上就是建立起 targetMap 和 ReactiveEffect 之前的关系,让我们可以根据指定对象的指定属性,来找到该属性对应的 ReactiveEffect
    • 而 ReactiveEffect 实例中存在一个 fn 函数,就是我们 effect 的第一个回调函数,这里就是当前触发代理对象 getter 行为时的函数
    • 这时候,指定对象的指定属性 与 触发getter行为的回调函数建立起了关系, 这时候完成了依赖的收集过程
  • 在我们业务代码中,effect 回调中进行了 getter 操作,而 setTimeout 中进行了 setter 操作, 我们回过头来再看一下这个 setter

    • 再来看下 set 同上述 get 类似
      const set = /*#__PURE__*/ createSetter()
      
      • 这里 set 是 createSetter 返回的一个 set 函数
        function createSetter(shallow = false) {return function set(target: object,key: string | symbol,value: unknown,receiver: object): boolean {let oldValue = (target as any)[key]if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {return false}if (!shallow) {if (!isShallow(value) && !isReadonly(value)) {oldValue = toRaw(oldValue)value = toRaw(value)}if (!isArray(target) && isRef(oldValue) && !isRef(value)) {oldValue.value = valuereturn true}} else {// in shallow mode, objects are set as-is regardless of reactive or not}const hadKey =isArray(target) && isIntegerKey(key)? Number(key) < target.length: hasOwn(target, key)// 注意,这里 result 是 true,表示 赋值成功了const result = Reflect.set(target, key, value, receiver)// don't trigger if target is something up in the prototype chain of originalif (target === toRaw(receiver)) {if (!hadKey) {trigger(target, TriggerOpTypes.ADD, key, value)} else if (hasChanged(value, oldValue)) {// 这里会触发trigger(target, TriggerOpTypes.SET, key, value, oldValue)}}return result}
        }
        
  • 响应性的核心是依赖收集和触发依赖的过程,上述的 trigger 就是触发依赖,我们看下这个函数,在 effect.ts 中

    export function trigger(target: object,type: TriggerOpTypes,key?: unknown,newValue?: unknown,oldValue?: unknown,oldTarget?: Map<unknown, unknown> | Set<unknown>
    ) {// 这里,基于 targetMap 获取 mapconst depsMap = targetMap.get(target)if (!depsMap) {// never been trackedreturn}let deps: (Dep | undefined)[] = []if (type === TriggerOpTypes.CLEAR) {// collection being cleared// trigger all effects for targetdeps = [...depsMap.values()]} else if (key === 'length' && isArray(target)) {depsMap.forEach((dep, key) => {if (key === 'length' || key >= (newValue as number)) {deps.push(dep)}})} else {// schedule runs for SET | ADD | DELETE// 这里会执行if (key !== void 0) {deps.push(depsMap.get(key)) // 将 set 集合 存放于 deps 中}// also run for iteration key on ADD | DELETE | Map.SET// 这里会执行匹配switch (type) {case TriggerOpTypes.ADD:if (!isArray(target)) {deps.push(depsMap.get(ITERATE_KEY))if (isMap(target)) {deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))}} else if (isIntegerKey(key)) {// new index added to array -> length changesdeps.push(depsMap.get('length'))}breakcase TriggerOpTypes.DELETE:if (!isArray(target)) {deps.push(depsMap.get(ITERATE_KEY))if (isMap(target)) {deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))}}break/// 这里匹配到了case TriggerOpTypes.SET:// 但是我们的target不是一个map对象,此处会跳过if (isMap(target)) {deps.push(depsMap.get(ITERATE_KEY))}break}}const eventInfo = __DEV__? { target, type, key, newValue, oldValue, oldTarget }: undefined// 这里会执行if (deps.length === 1) {if (deps[0]) {if (__DEV__) {// 这里会执行,这是核心的依赖触发triggerEffects(deps[0], eventInfo)} else {triggerEffects(deps[0])}}} else {const effects: ReactiveEffect[] = []for (const dep of deps) {if (dep) {effects.push(...dep)}}if (__DEV__) {triggerEffects(createDep(effects), eventInfo)} else {triggerEffects(createDep(effects))}}
    }
    
  • 我们看看 triggerEffects 发生了什么?

    export function triggerEffects(dep: Dep | ReactiveEffect[], // 这个就是我们的 Set 集合debuggerEventExtraInfo?: DebuggerEventExtraInfo
    ) {// spread into array for stabilization// 这里的 effects 是 ReactiveEffect 数组const effects = isArray(dep) ? dep : [...dep]// 我们可以看到下面两个for循环,是可以优化的,这样拆开写意义并不是很大for (const effect of effects) {if (effect.computed) {triggerEffect(effect, debuggerEventExtraInfo)}}for (const effect of effects) {// 这里会执行if (!effect.computed) {triggerEffect(effect, debuggerEventExtraInfo)}}
    }
    
  • 同样,我们可以看到 triggerEffect 发生了什么

    function triggerEffect(effect: ReactiveEffect,debuggerEventExtraInfo?: DebuggerEventExtraInfo
    ) {if (effect !== activeEffect || effect.allowRecurse) {if (__DEV__ && effect.onTrigger) {effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))}if (effect.scheduler) {effect.scheduler()} else {// 这里会执行,在上述run代码中,本质是在触发我们的 fn 函数,也就是 effect 的第一个回调函数,也随即会触发代理对象的 getter 行为effect.run()}}
    }
    
  • 我们看下我们业务代码中的 fn 函数

    document.querySelector('#app').innerText = obj.name
    
  • 由上可知,我们的页面会随即更新 obj.name,也就是业务代码中定时器中的setter后的值,上述这行代码,在执行 obj.name 时会再次触发 getter 行为
  • 直到页面上渲染出新的值,在整个 setter 行为中,主要做了如下工作
    • 修改 obj 的值
    • 调用 targetMap 下保存的 fn
  • 当我们的业务代码中,执行了 effect 回调的时候,发生了什么呢?

3 )进行effect的debug

  • 在chrome的Sources面板的Pages子面板找到 effect.ts 中,找到 effect() 方法,打上一个断点

  • 当走到了断点处, 程序是这样走的:

    // 在业务代码中,我们只传递了第一个参数,回调函数
    export function effect<T = any>(fn: () => T,options?: ReactiveEffectOptions
    ): ReactiveEffectRunner {if ((fn as ReactiveEffectRunner).effect) {fn = (fn as ReactiveEffectRunner).effect.fn}// 这里把我们自己业务代码中的回调传递过来,基于 ReactiveEffect 类构造出了一个 _effect 实例const _effect = new ReactiveEffect(fn)// 这里 options 是 undefined 不存在跳过if (options) {extend(_effect, options)if (options.scope) recordEffectScope(_effect, options.scope)}// 这里会匹配,执行实例的 run 方法if (!options || !options.lazy) {_effect.run()}const runner = _effect.run.bind(_effect) as ReactiveEffectRunnerrunner.effect = _effectreturn runner
    }
    
  • 我们这里进入 ReactiveEffect 这个类中,看下

    export class ReactiveEffect<T = any> {active = truedeps: Dep[] = []parent: ReactiveEffect | undefined = undefined/*** Can be attached after creation* @internal*/computed?: ComputedRefImpl<T>/*** @internal*/allowRecurse?: boolean/*** @internal*/private deferStop?: booleanonStop?: () => void// dev onlyonTrack?: (event: DebuggerEvent) => void// dev onlyonTrigger?: (event: DebuggerEvent) => voidconstructor(public fn: () => T, // 这里的fn就是我们传入的函数public scheduler: EffectScheduler | null = null,scope?: EffectScope) {recordEffectScope(this, scope)}// 着重关注下这里run() {if (!this.active) {return this.fn()}let parent: ReactiveEffect | undefined = activeEffectlet lastShouldTrack = shouldTrackwhile (parent) {if (parent === this) {return}parent = parent.parent}try {// 这个this是 ReactiveEffect 类的实例,将当前被激活的 effect 挂载到当前实例的parent上this.parent = activeEffect// 此时,activeEffect 会指向当前实例,其中被挂载的fn函数,就是我们业务代码中的 effect 的第一个回调参数activeEffect = thisshouldTrack = truetrackOpBit = 1 << ++effectTrackDepthif (effectTrackDepth <= maxMarkerBits) {initDepMarkers(this)} else {cleanupEffect(this)}// 注意这里的return,就是我们自己业务代码中的回调参数那个箭头函数, 一旦这个函数被执行,就会触发 代理对象的 getter 行为// 就是会触发 上述 get 属性,也就是 createGetter 方法return this.fn()} finally {if (effectTrackDepth <= maxMarkerBits) {finalizeDepMarkers(this)}trackOpBit = 1 << --effectTrackDepthactiveEffect = this.parentshouldTrack = lastShouldTrackthis.parent = undefinedif (this.deferStop) {this.stop()}}}stop() {// stopped while running itself - defer the cleanupif (activeEffect === this) {this.deferStop = true} else if (this.active) {cleanupEffect(this)if (this.onStop) {this.onStop()}this.active = false}}
    }
    
  • 回顾下 effect 做的几件事情

    • 生成 ReactiveEffect 实例
    • 触发 fn 方法,从而激活 getter
    • 建立 targetMap 和 activeEffect 之间的联系
      • dep.add(activeEffect)
      • activeEffect.deps.push(dep)
  • 综上,我们基于业务模块,基于如下流程,来梳理了 reactive 响应性核心代码

    • reactive 函数
    • effect 函数
    • 触发setter
  • 其实上述过程我们可以简化为

    • 创建 proxy
    • 收集 effect 依赖
    • 触发收集的依赖