vue3响应式分析

vue3响应式分析

  1. 首先对vue3响应式分析之前,需要对前置知识ProxyReflect有所了解,,关于这两个知识我推荐看阮一峰老师的ES6入门教程
  2. vue3的响应式我是以reactive为入口进行梳理,流程如下图
    在这里插入图片描述
  3. 源码位置:reactivity/src/...,分四部分解析
  • reactive文件:目标对象转化为proxy实例
  • baseHandler文件:基本类型处理器
  • collectionHandlers文件:Map、Set处理器
  • effect文件:收集触发依赖
  1. 如果不想看源码解析,可以直接看总结🐶

reactive

  • reactive:将一个JS对象转为具有响应式的proxy实例
    export function reactive(target: object) {
        // 如果是只读数据,就直接返回
        if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
          return target
        }
        return createReactiveObject(
          target,
          false,
          mutableHandlers,
          mutableCollectionHandlers,
          reactiveMap
        )
    }
  • createReactiveObject
    function createReactiveObject(
        target: Target, // 源对象
        isReadonly: boolean, // 是否只读
        baseHandlers: ProxyHandler<any>, // 基本类型的handlers
        collectionHandlers: ProxyHandler<any>, // 主要针对(set、map、weakSet、weakMap)的handlers
        proxyMap: WeakMap<Target, any>
    ) {
      // 如果不是一个对象,直接返回,并且在开发环境发出警告
        if (!isObject(target)) {
          if (__DEV__) {
            console.warn(`value cannot be made reactive: ${String(target)}`)
          }
          return target
        }
        // 如果已经是响应式,直接返回
        if (
          target[ReactiveFlags.RAW] &&
          !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
        ) {
          return target
        }
    
        // 如果目标对象已经存在代理,直接返回
        const existingProxy = proxyMap.get(target)
        if (existingProxy) {
          return existingProxy
        }
    
        // 如果类型值不是Object、Array、Map、Set、WeakMap、WeakSet的,直接返回
        const targetType = getTargetType(target)
        if (targetType === TargetType.INVALID) {
          return target
        }
    
        // 根据不同的类型值赋予不同的handlers,就是我之前图上画的分开处理
        /* 
          把set、Map这种数据与基础数据分开处理,是因为Map、Set中存储的数据必须通过this进行访问
          但是被proxy劫持后,this就变成了proxy,
          所以需要特殊处理,把劫持方法进行重写
         */
        const proxy = new Proxy(
          target,
          targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
        )
        proxyMap.set(target, proxy)
        return proxy
      }

baseHandler

baseHandler主要分析reactive的处理器对象mutableHandlers

// 对get set delete has onwKeys做了拦截处理
export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}

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.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
            ? shallowReactiveMap
            : reactiveMap
        ).get(target)
    ) {
      return target
    }

    const targetIsArray = isArray(target)

    // 如果target是数组并且key属于一些数组的原始方法,即触发拦截
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

    const res = Reflect.get(target, key, receiver)

    // 如果key是symbol的内置方法,或者是原型对象,直接返回
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    // 只读对象不收集依赖,因为不会触发依赖更新
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }

    // 浅层响应立即返回,不递归转化
    if (shallow) {
      return res
    }

    // 如果是ref对象(数组除外),返回真正的值,
    if (isRef(res)) {
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }

    if (isObject(res)) {
      // 由于proxy只能代理一层,所以target[key]的值如果是对象,就继续对其进行代理
      return isReadonly ? readonly(res) : reactive(res)
    }
    return res
  }
}

数组方法拦截: 对数组的两种原生方法进行了拦截

  • 遍历查找的方法:includes、indexOf、lastIndexOf
  • 改变数组长度的方法:push、pop、shift、unshift、splice
    function createArrayInstrumentations() {
        const instrumentations: Record<string, Function> = {}
        ;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
          const method = Array.prototype[key] as any
          instrumentations[key] = function(this: unknown[], ...args: unknown[]) {
            // 这一步是为了取原始实例,因为当前的this是receiver
            const arr = toRaw(this)
            // 搜集依赖
            for (let i = 0, l = this.length; i < l; i++) {
              track(arr, TrackOpTypes.GET, i + '')
            }
            // 触发方法,如果没有找到对应的值,就取原始值再遍历
            const res = method.apply(arr, args)
            if (res === -1 || res === false) {
              return method.apply(arr, args.map(toRaw))
            } else {
              return res
            }
          }
        })
        /* 
          因为改变数组长度的方法,执行期间会触发length的get和set
          就回导致无限循环track和trigger
          所以就用pauseTracking()禁用依赖收集,触发方法后,
          再用resetTracking()恢复track
         */
        ;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
          const method = Array.prototype[key] as any
          instrumentations[key] = function(this: unknown[], ...args: unknown[]) {
            pauseTracking()
            const res = method.apply(this, args)
            resetTracking()
            return res
          }
        })
        return instrumentations
    }

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 (!shallow) {
      value = toRaw(value)
      oldValue = toRaw(oldValue)
      // 如果原来的值是ref,但新的值不是,则将新的值赋给oldValue.value
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value
        return 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)
    const result = Reflect.set(target, key, value, receiver)
    
    // 判断receiver是当前对象的proxy实例,防止原型链上的proxy触发更新
    if (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
  }
}

deleteProperty、has…

  • deleteProperty、has、ownKeys的源码就不贴了,都是判断key的属性,然后选择触发或者收集依赖

collectionHandlers

  • collection主要分析reactive的重写方法对象mutableInstrumentations
    // 主要对以下原生api进行了改写
    const mutableInstrumentations: Record<string, Function> = {
        get(this: MapTypes, key: unknown) {
          return get(this, key)
        },
        get size() {
          return size((this as unknown) as IterableCollections)
        },
        has,
        add,
        set,
        delete: deleteEntry,
        clear,
        forEach: createForEach(false, false)
    }

get

function get(
    target: MapTypes,
    key: unknown,
    isReadonly = false,
    isShallow = false
) {
    // 获取原始值重新赋值给target
    target = (target as any)[ReactiveFlags.RAW]

    // 对target源对象和key进一步获取原始值
    const rawTarget = toRaw(target)
    const rawKey = toRaw(key)

    // 当前key和原始key均进行依赖收集(track)
    if (key !== rawKey) {
      !isReadonly && track(rawTarget, TrackOpTypes.GET, key)
    }
    !isReadonly && track(rawTarget, TrackOpTypes.GET, rawKey)
    const { has } = getProto(rawTarget)

    // 获取对应的转换函数
    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
    /* 
      如果源对象有key对应的属性,就通过原生get方法取到值,
      并对该值进行响应式转换,返回转换后的响应式对象,
      如果没有,就去key原始值中去查找
    */
    if (has.call(rawTarget, key)) {
      return wrap(target.get(key))
    } else if (has.call(rawTarget, rawKey)) {
      return wrap(target.get(rawKey))
    } else if (target !== rawTarget) {
      target.get(key)
    }
}

size

// 对size属性做get拦截
function size(target: IterableCollections, isReadonly = false) {
  target = (target as any)[ReactiveFlags.RAW]
  // 获取size和获取数组的length类似,都用专门的key做依赖收集
  !isReadonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
  return Reflect.get(target, 'size', target)
}

set

function set(this: MapTypes, key: unknown, value: unknown) {
  // 获取value和this上下文的原始值
  value = toRaw(value)
  const target = toRaw(this)
  const { has, get } = getProto(target)

  /* 
    判断源对象是否已经存在对应的key
    1. 首先查找源对象是否已有key对应的属性
    2. 如果没有,再查找key对应的原始值在源对象的属性是否存在
  */
  let hadKey = has.call(target, key)
  if (!hadKey) {
    key = toRaw(key)
    hadKey = has.call(target, key)
  } else if (__DEV__) {
    checkIdentityKeys(target, has, key)
  }

  const oldValue = get.call(target, key)
  target.set(key, value)

  // 触发依赖,新增属性和修改属性分开进行trigger
  if (!hadKey) {
    trigger(target, TriggerOpTypes.ADD, key, value)
  } else if (hasChanged(value, oldValue)) {
    trigger(target, TriggerOpTypes.SET, key, value, oldValue)
  }
  return this
}

has、clear…

  • 其余重写方法我就不上代码了,不同点是单个属性触发单个的依赖,如果是遍历所有属性的方法就触发所有依赖

effect

  • effect文件作为响应式的核心,主要负责收集依赖,触发依赖

effect

  • effect函数主要是生成收集依赖所需的依赖函数
    export function effect<T = any>(
        fn: () => T,
        options: ReactiveEffectOptions = EMPTY_OBJ
    ): ReactiveEffect<T> {
        // 如果已经是effect函数,取得原来的fn
        if (isEffect(fn)) {
          fn = fn.raw
        }
        const effect = createReactiveEffect(fn, options)
    
        // 如果lazy为false,立即执行一次
        if (!options.lazy) {
          effect()
        }
        return effect
    }
  • createReactiveEffect:生成effect对象
    function createReactiveEffect<T = any>(
        fn: () => T,
        options: ReactiveEffectOptions
    ): ReactiveEffect<T> {
        const effect = function reactiveEffect(): unknown {
          // 没有激活,已经调用stop函数停止监听
          if (!effect.active) {
            return fn()
          }
          /**
           * effectStack是一个全局的effect栈结构
           * 设计为栈结构是因为如果effect是嵌套时,为了防止内层副作用函数覆盖外层副作用函数,在收集时只收集栈顶的,这样就不会收集到错误的副作用函数
           */
          if (!effectStack.includes(effect)) {
            /**
             * 为了保证当前effect的dep是最新,因为在一些判断处理中,可能会导致一些无效的副作用函数
             * 所以为了取消这些不必要的更新,就要清除effect依赖
             */
            cleanup(effect)
            try {
              enableTracking() // 重新收集依赖
              effectStack.push(effect)
              activeEffect = effect
              return fn()
            } finally {
              /* 
                track将依赖函数activeEffect添加到对应的dep中,
                然后将activeEffect重置为上一个effect的值
              */
              effectStack.pop()
              resetTracking()
              activeEffect = effectStack[effectStack.length - 1]
            }
          }
        } as ReactiveEffect
        effect.id = uid++ // 自增ID
        effect.allowRecurse = !!options.allowRecurse // 递归状态
        effect._isEffect = true // 用于标识方法是不是effect
        effect.active = true // 用于判断当前effect是否激活,有一个stop()来将它设为false
        effect.raw = fn // effect的执行函数
        effect.deps = [] // 用于收集依赖
        effect.options = options // 创建effect传入的options
        return effect
    }
  • activeEffect就是标记track所需的依赖函数

track

  • track就是baseHandler和collectionHandlers文件中频繁使用的收集依赖函数
  • 首先需要看一个关键变量targetMap
    // targetMap是依赖管理中心,收集依赖和触发依赖都依托于这个Map数据
    // 下面是targetMap的定义(target -> key -> dep)
    // target: 监听的对象源
    // key: 监听的键值
    // dep:依赖函数
    type Dep = Set<ReactiveEffect>
    type KeyToDepMap = Map<any, Dep>
    const targetMap = new WeakMap<any, KeyToDepMap>()
    // 格式大致为
    targetMap = {
      target: {
        key1: { fn1, fn2 }
        key2: { fn1, fn2 }
      }
    }
    /**
     * @param {target} 目标对象
     * @param {type} 收集类型
     * @param {key} 需要收集依赖的key
     */
    export function track(target: object, type: TrackOpTypes, key: unknown) {
        // activeEffect为空,就表示当前没有依赖,就没必要做依赖收集了
        if (!shouldTrack || activeEffect === undefined) {
          return
        }
        // 获取当前依赖数据
        let depsMap = targetMap.get(target)
    
        if (!depsMap) {
          targetMap.set(target, (depsMap = new Map()))
        }
    
        // 如果当前数据中没有所属的依赖key,就重新设置一个
        let dep = depsMap.get(key)
        if (!dep) {
          depsMap.set(key, (dep = new Set()))
        }
        // 添加依赖函数
        if (!dep.has(activeEffect)) {
          dep.add(activeEffect)
          activeEffect.deps.push(dep)
          if (__DEV__ && activeEffect.options.onTrack) {
            activeEffect.options.onTrack({
              effect: activeEffect,
              target,
              type,
              key
            })
          }
        }
    }

trigger

// 触发依赖
export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)
  // 如果没有收集过依赖,直接返回
  if (!depsMap) {
    return
  }

  const effects = new Set<ReactiveEffect>()
  const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
    if (effectsToAdd) {
      effectsToAdd.forEach(effect => {
        // 避免循环触发依赖 类似`effect(() => obj.foo++)`
        if (effect !== activeEffect || effect.allowRecurse) {
          effects.add(effect)
        }
      })
    }
  }

  if (type === TriggerOpTypes.CLEAR) {
    // 在清空前,将对应的依赖全部添加到局部Set
    depsMap.forEach(add)
  } else if (key === 'length' && isArray(target)) {
    // 当数组的length属性变化时触发
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= (newValue as number)) {
        add(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    // 往相应队列添加依赖
    if (key !== void 0) {
      add(depsMap.get(key))
    }

    // also run for iteration key on ADD | DELETE | Map.SET
    // 通过不同的TriggerOpTypes将depsMap的数据取出,添加到effects
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          add(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          add(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
          add(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {
          add(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  const run = (effect: ReactiveEffect) => {
    if (__DEV__ && effect.options.onTrigger) {
      effect.options.onTrigger({
        effect,
        target,
        key,
        type,
        newValue,
        oldValue,
        oldTarget
      })
    }
    if (effect.options.scheduler) {
      // 如果有调度属性,就通过scheduler处理执行
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }

  effects.forEach(run)
}

总结

  • vue3响应式是通过数据劫持和发布订阅的模式进行处理,首先vue3中的数据是通过proxy做了一层代理,然后处理proxyhandler,基本类型的handler是通过baseHandlers,特殊类型(map,set)的handler是通过collectHandlers
  • handler中获取属性的操作通过track进行依赖收集,修改属性的操作通过trigger进行依赖触发,依赖的收集与触发是通过依赖管理中心targetMap保存的
  • track进行收集时,他收集的是activeEffect,这个变量存储的就是正在触发的副作用函数,activeEffect通过effect()方法进行收集
  • effect()常用的三个地方
    • 组件副作用函数
    • watch
    • computed