序言

vue3对整个响应式都做了一次重大升级,有原来的defineProperty,改为全新的Proxy进行重构。

先看看defineProperty的局限性

  • 对数组的操作有局限性,仅对Array下的方法进行hack处理
  • 基于属性拦截,初始化时会递归全部的属性,对性能稍有影响,且对未有的属性监听不到

而Proxy的出现恰恰弥补了上面的缺陷。

源码结构

  1. ├─packages
  2. ├─reactivity
  3. ├─src
  4. ├─baseHandlers.ts
  5. ├─collectionHandlers.ts
  6. ├─computed.ts
  7. ├─effect.ts
  8. ├─operations.ts
  9. ├─reactive.ts
  10. ├─ref.ts

目标源对象

vue3的响应式flag,用于标记目标target对象,下面是枚举的属性:

export const enum ReactiveFlags {
  SKIP = '__v_skip',
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly',
  RAW = '__v_raw'
}

export interface Target {
  [ReactiveFlags.SKIP]?: boolean  // 跳过,不对target做响应式处理
  [ReactiveFlags.IS_REACTIVE]?: boolean   // target是响应式的
  [ReactiveFlags.IS_READONLY]?: boolean   // target是只读的
  [ReactiveFlags.RAW]?: any   // target已经是Proxy对象时会有该属性,表示Proxy对应的源数据
}

😀 我们来一一解析它。

baseHandlers.ts

该文件主要是组合Proxy的基础配置参数:
有两个重要的方法 createGetter 和 createSetter。

createGetter

// createGetter
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 (
      // receiver指向调用者,这里的判断是为了保证触发拦截handler的是proxy对象本身而非proxy的继承者。
      // 触发拦截器的两种途径:
      // 1 访问proxy对象本身的属性;
      // 2 访问对象原型链上有proxy对象的对象的属性,因为查询属性会沿着原型链向下游依次查询,因此同样会触发拦截器
      key === ReactiveFlags.RAW && receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
    ) {
      // 返回target本身,即响应式对象的原始值
      return target
    }

    // 访问Array对象上的方法,存储在 arrayInstrumentations 工具集里
    const targetIsArray = isArray(target)

    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      // 这里在调用indexOf等数组方法时是通过proxy来调用的,因此
      // arrayInstrumentations[key]的this一定指向proxy实例
      // 也即receiver
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

    // Proxy预返回值
    const res = Reflect.get(target, key, receiver)

    // key是symbol或访问的是__proto__属性不做依赖收集和递归响应式转化,直接返回结果
    if (
      isSymbol(key)
        ? builtInSymbols.has(key as symbol)
        : key === `__proto__` || key === `__v_isRef`
    ) {
      return res
    }

    // 不是只读target才需收集依赖,
    // 只读的因为属性不会变化,因此无法触发setter,也就不会触发依赖更新
    if (!isReadonly) {
      // 通过track函数将依赖存储到对应的全局仓库中
      track(target, TrackOpTypes.GET, key)
    }

    // 浅比较不做递归转化
    if (shallow) {
      return res
    }

    // 访问属性已经是ref对象,保证访问ref属性时得到的是ref对象的value属性值,数组除外
    if (isRef(res)) {
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }

    // 如果子元素是对象,需要递归进行响应式转化
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

createSetter

// createSetter
function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    const oldValue = (target as any)[key]
    if (!shallow) {
      // 通过setter入参的新值获取它的原始值,新传入值可能是响应式数据,如果直接和
      // target上的原始值比较是没有任何实际意义的
      value = toRaw(value)
       // target不是数组,且旧值为ref,新值非ref,直接将ref.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实例才更新派发,防止通过原型链触发拦截器触发更新。
    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
  }
}

用法:

const get = /*#__PURE__*/ createGetter()
const set = /*#__PURE__*/ createSetter()

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}

这里的mutableHandlers就会暴露给createReactiveObject那边。

arrayInstrumentations

vue3单独处理数组的工具集:

const arrayInstrumentations: Record<string, Function> = {}

// 获取索引的方法
(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
  const method = Array.prototype[key] as any
  arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) {
     // 去掉响应式wrapper,获取源数组对象
    const arr = toRaw(this)

    // 按照数组下标收集依赖
    for (let i = 0, l = this.length; i < l; i++) {
      track(arr, TrackOpTypes.GET, i + '')
    }
    // 优先调用Array原生方法查找传入值
    const res = method.apply(arr, args)
    if (res === -1 || res === false) {
      // 没有查询到对应的值,有可能是包装后的响应式数据,有wrapper,引用不同所以
      // 有可能查不到,尝试去掉wrapper再查询
      return method.apply(arr, args.map(toRaw))
    } else {
      return res
    }
  }
})

// 改变数组长度的方法
(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
  const method = Array.prototype[key] as any
  arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) {
    // 执行前禁用依赖收集,
    /**
     * 这里主要是为了避免改变数组长度时,会set length,形成track - trigger的死循环
     * 因此要暂停改变数组长度时的执行期间收集依赖
     */
    pauseTracking()
    const res = method.apply(this, args)
    // 执行后恢复之前track状态
    resetTracking()
    return res
  }
})

effect.ts

该文件主要是处理副作用,主要是以下内容:

  • 创建 effect
  • track 依赖收集器
  • trigger 派发
  • clearup 清除
  • stop 停止effect
  • trackStack 收集栈的暂停、恢复、重置处理

createReactiveEffect

创建effect函数:
参数fn是执行函数,会触发target[key]的getter,完成依赖收集。
在依赖收集前后采用栈数据结构(effectStack)来做effect的执行调度,保证当前effect的优先级最高,并及时清除已收集依赖的内存。

const effectStack: ReactiveEffect[] = []
let activeEffect: ReactiveEffect | undefined

function createReactiveEffect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions
): ReactiveEffect<T> {
  const effect = function reactiveEffect(): unknown {
    if (!effect.active) {
      return options.scheduler ? undefined : fn()
    }
    // effectStack是一个全局effect栈,用于effect调度。
    if (!effectStack.includes(effect)) {
      // 先清除,防止effect重复添加
      cleanup(effect)
      try {
        // 在setup函数自行期间,暂停依赖收集,因此这里需要恢复
        enableTracking()
        // effect入栈
        effectStack.push(effect)
        // 将当前effect设置为全局激活effect,在getter中会收集activeEffect持有的effect
        // activeEffect表示当前依赖收集系统正在处理的effect
        activeEffect = effect
        // 执行effect的执行函数,比如访问target[key],会触发getter
        return fn()
      } finally {
        // 此时fn执行完毕,依赖已收集到依赖仓库,因此将当前effect出栈,及时释放内存
        effectStack.pop()
        resetTracking()
        // 将activeEffect恢复到effect栈中最后一个effect,即上次的activeEffect,继续
        // 做该effect的收集工作
        activeEffect = effectStack[effectStack.length - 1]
      }
    }
  } as ReactiveEffect
  effect.id = uid++  // effect的uid
  effect.allowRecurse = !!options.allowRecurse   
  effect._isEffect = true   // 标记是否是effect
  effect.active = true      // effect睡否处于激活状态
  effect.raw = fn           // effect的执行函数
  effect.deps = []          // 当前effect的dep数组
  effect.options = options  // effect的配置项,包含配置项及hooks
  return effect
}

track

依赖收集器:
这里看思维导图 https://www.yuque.com/qqhh/gb15wo/rl55vs

const targetMap = new WeakMap<any, KeyToDepMap>()

export function track(target: object, type: TrackOpTypes, key: unknown) {
  // 如果当前没有激活的effect,就不用收集
  // shouldTrack===false 时,暂停收集,详情见下面的trackStack
  if (!shouldTrack || activeEffect === undefined) {
    return
  }
  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()))
  }
  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

派发:trigger是track收集相对应的触发器。

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
  }

  // 普通effect集合
  const effects = new Set<ReactiveEffect>()  

  // effect的集合函数
  // add函数可以过滤出符合条件的依赖,然后批量触发
  const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
    if (effectsToAdd) {
      effectsToAdd.forEach(effect => {
        /**
            这里判断effect !== activeEffect的原因:
          不能和当前的effect相同,是因为有一种情况有问题: count.value++
          如果这是个effect,它会触发 getter,track收集了当前激活的effect,
          然后 count.value = count.value+1 会触发 setter,并且执行trigger,
          会陷入一个死循环,所以要过滤当前的effect。
        */
        if (effect !== activeEffect || effect.allowRecurse) {
          effects.add(effect)
        }
      })
    }
  }

  if (type === TriggerOpTypes.CLEAR) {
    // 清空依赖
    depsMap.forEach(add)
  } else if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= (newValue as number)) {
        // 源对象是数组且trigger的key是length,需要触发length对应的effects,
        // 同时,由于触发length时一定是数组长度变化了,当数组长度变短时,需要
        // 做已删除数组元素的effects的trigger工作,也就是索引号 >= 数组
        // 最新length的元素们所对应的effects,对它们做清除后的批量触发。
        add(dep)
      }
    })
  } else {
    // key不是undefined就会触发依赖局部添加,只不过如果是新增属性对应的dep为空
    if (key !== void 0) {
      add(depsMap.get(key))
    }

    // 新增属性或者删除属性的处理
    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)) {
          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
    }
  }

   // run函数用来批量执行add方法添加过的effect
  const run = (effect: ReactiveEffect) => {
    if (__DEV__ && effect.options.onTrigger) {
      effect.options.onTrigger({
        effect,
        target,
        key,
        type,
        newValue,
        oldValue,
        oldTarget
      })
    }
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }

  effects.forEach(run)
}

clearup

清除一个effect:

function cleanup(effect: ReactiveEffect) {
  const { deps } = effect // effect上面的收集数组
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    deps.length = 0
  }
}

effect

暴露一个创建effect的方法:

export function effect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
  if (isEffect(fn)) {
    fn = fn.raw
  }
  const effect = createReactiveEffect(fn, options)
  // options.lazy 默认是false,自执行一次effect
  if (!options.lazy) {
    effect()
  }
  return effect
}

stop

暴露一个停止(清除)effect的方法:

export function stop(effect: ReactiveEffect) {
  if (effect.active) {
    cleanup(effect)
    if (effect.options.onStop) {
      effect.options.onStop()
    }
    effect.active = false
  }
}

trackStack

收集栈的一些方法:暂停如栈、恢复入栈、恢复到最后一次栈

let shouldTrack = true
const trackStack: boolean[] = []

export function pauseTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = false
}

export function enableTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = true
}

export function resetTracking() {
  const last = trackStack.pop()
  shouldTrack = last === undefined ? true : last
}

reactive.ts_

该文件是响应式的核心,主要有以下内容:

  • createReactiveObject 创建响应式对象
  • shallowReactive 创建浅层(第一层)的响应式,不深度递归
  • readonly 创建只读响应式代理
  • 判断是否响应式对象
  • 判断是否是只读的对象
  • toRaw 获取响应式源对象
  • markRaw 创建一个永远不会被响应式代理的对象

createReactiveObject

createReactiveObject 将target转换为响应式对象。

// 将target转化为响应式对象
  function createReactiveObject(
    target,            // 源对象
    isReadonly,        // 是否只读
    baseHandlers,      // proxy的handlers,来源baseHandlers.ts
    collectionHandlers // 来源collectionHandlers.ts
  ) {
    // target只能是object
    if (!isObject(target)) {
      {
        console.warn(`value cannot be made reactive: ${String(target)}`)
      }
      return target
    }
    // __v_raw仅当数据源已经是Proxy时才会有
    // 判断如果target已经是响应式了直接返回
    if (
      target['__v_raw' /* RAW */] &&
      !(isReadonly && target['__v_isReactive' /* IS_REACTIVE */])
    ) {
      return target
    }
    // 判断是否是只读
    const proxyMap = isReadonly ? readonlyMap : reactiveMap
    const existingProxy = proxyMap.get(target)
    // 如果该target已经在map中了
    if (existingProxy) {
      return existingProxy
    }
    // 不在响应式的白名单,直接返回
    const targetType = getTargetType(target)
    if (targetType === 0 /* INVALID */) {
      return target
    }
    // 将target转换为proxy
    const proxy = new Proxy(
      target,
      targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers
    )
    proxyMap.set(target, proxy)
    return proxy
  }

isReactive

判断是否是一个响应式对象;

export function isReactive(value: unknown): boolean {
  if (isReadonly(value)) {
    // 如果是只读的,取target源对象
    return isReactive((value as Target)[ReactiveFlags.RAW])
  }
  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}

isReadonly

判断是否是一个只读对象;

export function isReadonly(value: unknown): boolean {
  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}

isProxy

判断是否是响应式对象或只读对象 (他们都是proxy对象);

export function isProxy(value: unknown): boolean {
  return isReactive(value) || isReadonly(value)
}

toRaw

获取响应式源对象

export function toRaw<T>(observed: T): T {
  return (
    // ReactiveFlags.RAW 就是上面的指向目标源对象的key
    (observed && toRaw((observed as Target)[ReactiveFlags.RAW])) || observed
  )
}

computed.ts

自动计算属性。

computed

class ComputedRefImpl {
    constructor(getter, _setter, isReadonly) {
      this._setter = _setter
      this._dirty = true
      this.__v_isRef = true
      this.effect = effect(getter, {
        lazy: true,   // 初始化effect时,默认不执行getter
        scheduler: () => {
          // getter的时候,不派发通知
          if (!this._dirty) {
            this._dirty = true
            trigger(toRaw(this), 'set' /* SET */, 'value')
          }
        },
      })
      this['__v_isReadonly' /* IS_READONLY */] = isReadonly
    }
    get value() {
      if (this._dirty) {
        // 赋值
        this._value = this.effect()
        this._dirty = false
      }
      // compunted 收集依赖
      track(toRaw(this), 'get' /* GET */, 'value')
      return this._value
    }
    set value(newValue) {
      this._setter(newValue)
    }
  }

  function computed(getterOrOptions) {
    let getter
    let setter
    if (isFunction(getterOrOptions)) {
      getter = getterOrOptions
      setter = () => {
        console.warn('Write operation failed: computed value is readonly')
      }
    } else {
      getter = getterOrOptions.get
      setter = getterOrOptions.set
    }
    return new ComputedRefImpl(
      getter,
      setter,
      isFunction(getterOrOptions) || !getterOrOptions.set
    )
  }

看到 computed 的参数,我们发现有两种参数:

  • getterOrOptions 是函数,会生成effect进行依赖收集;
  • getterOrOptions 是对象,有 get和set属性,可实现自定义配置;

ref.ts

该文件主要是处理vue中ref的;

RefImpl

ref的实现类;

class RefImpl<T> {
  private _value: T

  // 没有ref实例下都有一个 __v_isRef 的只读属性,标识它是一个ref
  public readonly __v_isRef = true

  constructor(private _rawValue: T, public readonly _shallow = false) {
    // 如果不是浅比较,convert会判断如果是object,则调用 reactive 方法 (createReactiveObject)
    this._value = _shallow ? _rawValue : convert(_rawValue)
  }

  get value() {
    // ref.value,我们要这样取值,
    // 依赖收集器执行
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
  }

  set value(newVal) {
    // 比较新值和旧值
    if (hasChanged(toRaw(newVal), this._rawValue)) {
      this._rawValue = newVal
      // 值已更换,重新赋值
      // 派发通知
      this._value = this._shallow ? newVal : convert(newVal)
      trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
    }
  }
}

createRef

创建一个ref对象;

function isRef(r) {
  return Boolean(r && r.__v_isRef === true)
}

// 创建一个ref对象
function createRef(rawValue: unknown, shallow = false) {
  // 判断是否已经是一个ref对象
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

// 创建一个浅比较的ref
function shallowRef(value) {
  return createRef(value, true)
}

unref

卸载一个ref对象;

function unref(ref) {
  return isRef(ref) ? ref.value : ref
}

proxyRefs

创建一个Proxy对象的ref;

const shallowUnwrapHandlers: ProxyHandler<any> = {
  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
  set: (target, key, value, receiver) => {
    const oldValue = target[key]
    if (isRef(oldValue) && !isRef(value)) {
      oldValue.value = value
      return true
    } else {
      return Reflect.set(target, key, value, receiver)
    }
  }
}

export function proxyRefs<T extends object>(
  objectWithRefs: T
): ShallowUnwrapRef<T> {
  // 如果不是一个响应式对象,重新 new Proxy
  return isReactive(objectWithRefs)
    ? objectWithRefs
    : new Proxy(objectWithRefs, shallowUnwrapHandlers)
}

customRef

自定义ref,可以对其依赖项跟踪和派发进行显式控制。

class CustomRefImpl<T> {
  private readonly _get: ReturnType<CustomRefFactory<T>>['get']
  private readonly _set: ReturnType<CustomRefFactory<T>>['set']

  public readonly __v_isRef = true

  constructor(factory: CustomRefFactory<T>) {
    const { get, set } = factory(
      () => track(this, TrackOpTypes.GET, 'value'),
      () => trigger(this, TriggerOpTypes.SET, 'value')
    )
    this._get = get
    this._set = set
  }

  get value() {
    return this._get()
  }

  set value(newVal) {
    this._set(newVal)
  }
}

export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
  return new CustomRefImpl(factory) as any
}

实例用法:

<template>
    <input v-model="handleCustome">
</template>

<script>
import { customRef } from vue;
export default {
  setup(){
    const handleCustome = (value, delay = 200) => {
      let timer;
      // 这里的参数是工厂函数,接收2个参数一个track,一个trigger
      return customRef((track, trigger) => {
        return {
          get(){
            track();
            return value;
          },
          set(newValue){
            clearTimeout(timer);
            timer = setTimeout(()=>{ 
              value = newValue;
              trigger();
            }, delay)
          }
        }
      })
    }
    return {
      handleCustome: handleCustome('')
    }
  }
}
</script>

toRefs

可以将数据转换为ref对象。

function toRefs(object) {
  if (!isProxy(object)) {
    console.warn(
      `toRefs() expects a reactive object but received a plain one.`
    )
  }
  const ret = isArray(object) ? new Array(object.length) : {}
  for (const key in object) {
    ret[key] = toRef(object, key)
  }
  return ret
}

class ObjectRefImpl {
  constructor(_object, _key) {
    this._object = _object
    this._key = _key
    this.__v_isRef = true
  }
  get value() {
    return this._object[this._key]
  }
  set value(newVal) {
    this._object[this._key] = newVal
  }
}

function toRef(object, key) {
  return isRef(object[key]) ? object[key] : new ObjectRefImpl(object, key)
}

实例用法:

<template>
    <div>
      {{name}}
  </div>
</template>

<script>
import { reactive, toRefs } from vue;
export default {
  setup(){
    const foo = reactive({ name: 'Hello World' })
    return {
      ...toRefs(foo)
    }
    /**
      这里如果不用 toRefs的话,就只能这样用
            return { foo  }
      在模版里面这样用
      {{foo.name}}
      如果直接 return { ...foo } 的话,reactive的响应式就会失效,
      因为reactive实例下有很多属性,解构后就丢失了,所以就没有响应式数据了。
   */
  }
}
</script>

完!

👏 恭喜你,看到这里,有没有发现自己离大神又近了一步!!! 😜😜😜