Vue3 reactivity 模块源码解读

850 阅读13分钟

阅读源码带来的收获。

  • (1)ES6 的 Proxy、Reflect 组合语义化语法;
  • (2)TypeScript 的深度用法;
  • (3)Vue3 响应式原理;
  • (4)依赖收集和触发过程;
  • (5)Vue3 的 reactivity 模块的常用 API 实现。

阅读前所需要掌握的知识点。

  • 了解 ES6 Proxy API

  • 了解 ES6 Reflect API

  • 了解 TypeScript 的使用 Typescript tslang

  • 对于Vue2.x时候的 Object.defineProperty 响应式处理来说,Proxy 具备了更加强大的能力,新增、删除、迭代操作也能够监听到,而无需多余API进行新增、删除。

  • 而使用了 TypeScript 重构了整个项目,配合 monorepo 实现了模块的划分,其中响应式相关的原理都存放到了 reactivity 模块中。

1. reactivity 入口

reactivity 入口部分可以说只是做出了相关的导出操作

export { ref, toRefs /* ... */ } from "./ref";
export { reactive, readonly /* ... */ } from "./reactive";
export { computed /* ... */ } from "./computed";
export { effect, stop, trigger, track /* ... */ } from "./effect";
  • 核心部分在上面导出的几个 API。

  • 我们可以通过 reactive 去开始解读源码,从中展开到个个 API 实现。

1.1 reactive.ts 解读

  • (1)首先使用了 TypeScript 的函数重载,对类型进行的声明;
  • (2)然后通过判断有没有 ReactiveFlags.IS_READONLY 来调用 function createReactiveObject 创建 reactive 对象;
  • (3)通过判断是否可以被代理、是否有缓存、是否可以被代理进行处理;
  • (4)是否被代理的核心是调用 Object.prototype.toString.call 验证类型;
  • (5)最后进行代理,缓存后返回被代理的对象。
import { mutableHandlers /* ... */ } from "./baseHandlers";
import { mutableCollectionHandlers /* ... */ } from "./collectionHandlers";

export const reactiveMap = new WeakMap<Target, any>();

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>;
export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target;
  }
  return createReactiveObject(
    target, // 响应式的目标对象
    false, // 是否只读
    mutableHandlers, // reactive基本对象的代理处理
    mutableCollectionHandlers, // Map、Set等对象的代理
    reactiveMap // 缓存Map
  );
}
import { isObject, toRawType /* ... */ } from "@vue/shared";
// 拿到对象(WeakMap、Map、WeakSet、Set、Object、Array)的Type
function getTargetType(value: Target) {
  return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
    ? TargetType.INVALID
    : targetTypeMap(toRawType(value));
}
function createReactiveObject(
  target: Target, // 创建响应式的目标对象(可以是数组、对象、Map、Set...)
  isReadonly: boolean, // 是否是只读模式
  baseHandlers: ProxyHandler<any>, // 基本的Proxy代理函数
  collectionHandlers: ProxyHandler<any>, // Map、Set等的Proxy代理函数
  proxyMap: WeakMap<Target, any> // 代理的proxy缓存Map
) {
  // 非object情况直接返回
  if (!isObject(target)) {
    return target;
  }
  // 目标对象上存在 ReactiveFlags.RAW (调用过reactive/readonly函数)
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target;
  }
  // 目标对象被代理过存在缓存中
  const existingProxy = proxyMap.get(target);
  if (existingProxy) {
    return existingProxy;
  }
  // 获取类型,判断是否可以被代理
  const targetType = getTargetType(target);
  if (targetType === TargetType.INVALID) {
    return target;
  }
  // 通过拿到的类型验证是否是COLLECTION(Map、Set...)还是基本的类型(Object、Array)
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  );
  // 设置缓存并导出代理后的对象
  proxyMap.set(target, proxy);
  return proxy;
}
  • reactive.ts 其他 readonly、shallowReactive、shallowReadonly 也是类型的调用方式,在createReactiveObject的时候传递对应的处理对象、缓存 Map。

  • isReadonly、isReactive 是巧妙的验证对象上面添加的 ReactiveFlags 里面枚举的类型。

// ReactiveFlags 是一个 TypeScript 的枚举类型
export const enum ReactiveFlags {
  SKIP = "__v_skip",
  IS_REACTIVE = "__v_isReactive",
  IS_READONLY = "__v_isReadonly",
  RAW = "__v_raw",
}

export function isReactive(value: unknown): boolean {
  if (isReadonly(value)) {
    return isReactive((value as Target)[ReactiveFlags.RAW]);
  }
  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);
}

export function isReadonly(value: unknown): boolean {
  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);
}
  • reactive() 小总结:

    • reactive.ts 中主要是进行了验证、缓存的操作,处理了重复代理的问题、空间换时间的方式进行优化。
    • Proxy 的代理核心在baseHandlers.tscollectionHandlers.ts导出引用;
    • 调用了 @vue/shared 模块导出的 isObject、toRawType 等共享方法,这个模块的常用utils可以运用到我们实际项目开发当中;
  • 接下来就是我们的 Proxy 代理核心 baseHandlers.ts

1.2 baseHandlers.ts 解读

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys,
};
  • 代理了以上五个操作、对增删改查、验证、枚举操作进行处理

1.2.1 get 函数

  • (1)createGetter 通过判断,对自身定义的属性进行处理返回、对数组原型函数push、pop等进行处理;
  • (2)处理以上操作之后,通过 key 调用Reflect.get 拿到结果;
  • (3)ref 情况进行自动解构,直接返回数据源;
  • (4)后面还验证是否需要进行依赖收集、是否要对返回的对象数据进行深层代理。
import {
  isObject,
  hasOwn,
  isSymbol,
  /* ... */
  isArray,
  isIntegerKey,
  /* ... */
} from "@vue/shared";
// 执行高阶函数 createGetter 拿到对应的get函数
const get = /*#__PURE__*/ createGetter();

function createGetter(isReadonly = false, shallow = false) {
  // target(被代理的对象)、key、receiver(代理后的对象)
  return function get(target: Target, key: string | symbol, receiver: object) {
    // key 为 vue内部注入的ReactiveFlags,返回相关的true/false
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly;
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly;
    } else if (
      // 响应式情况, 通过ReactiveFlags.RAW获取数据,直接返回目标对象
      // 这个操作在toRaw函数、isReactive都有体现
      key === ReactiveFlags.RAW &&
      receiver === // 并且代理对象是否被缓存过
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target;
    }
    const targetIsArray = isArray(target);
    // 非只读 & 数组 & 属于重写的['push', 'pop', 'shift', 'unshift', 'splice'] | ['includes', 'indexOf', 'lastIndexOf']
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      // 直接拿相关的函数
      return Reflect.get(arrayInstrumentations, key, receiver);
    }
    // 获取数据
    const res = Reflect.get(target, key, receiver);
    // 验证是否存在相关不需要 Track 依赖收集
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res;
    }

    if (!isReadonly) {
      // 非只读状态下收集依赖
      track(target, TrackOpTypes.GET, key);
    }
    // 浅代理情况直接返回
    if (shallow) {
      return res;
    }
    // 对属于ref情况,判断是否存在 __v_isRef & 非数组 或非数值key,直接解构拿到.value
    if (isRef(res)) {
      // ref unwrapping - does not apply for Array + integer key.
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key);
      return shouldUnwrap ? res.value : res;
    }
    // 如果获取的数据是对象,进行深层次只读/代理
    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);
    }

    return res;
  };
}

1.2.2 set 函数

  • (1)createSetteroldValue是ref、newValue是非ref 情况进行自动赋值处理,简化了 reactive 里面嵌套 ref 情况的赋值操作(前面在获取的时候进行了解构);
  • (2)设置 newValue 之前,进行判断 key 是否存在,进行对应的触发TriggerOpTypes进行标明,在没有变化情况下不做处理。
// 一样通过createSetter创建set函数
const set = /*#__PURE__*/ createSetter();
function createSetter(shallow = false) {
  // target(被代理的对象)、key、value、receiver(代理后的对象)
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    // 拿到修改/添加之前的值
    let oldValue = (target as any)[key];
    if (!shallow) {
      // 拿到源值(toRaw这样处理是为了防止获取的值是一个Proxy,进行解构拿到被代理前的对象)
      value = toRaw(value);
      oldValue = toRaw(oldValue);
      // 属于老值是ref、新值非ref,直接赋值(get情况进行解构,set的时候进行自动赋值到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);
    // 进行设置新的value
    const result = Reflect.set(target, key, value, receiver);
    // don't trigger if target is something up in the prototype chain of original
    if (target === toRaw(receiver)) {
      // 设置之前没有这个key,就是添加操作,否则是修改操作(触发对应的trigger,就是触发收集的依赖)
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value);
      } else if (hasChanged(value, oldValue)) {
        // 判断有无变化(相同情况不触发依赖)
        trigger(target, TriggerOpTypes.SET, key, value, oldValue);
      }
    }
    return result;
  };
}

1.2.3 deleteProperty 函数 和 has、ownKey函数

function deleteProperty(target: object, key: string | symbol): boolean {
  const hadKey = hasOwn(target, key);
  const oldValue = (target as any)[key];
  const result = Reflect.deleteProperty(target, key);
  if (result && hadKey) {
    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue);
  }
  return result;
}
  • deleteProperty函数 就做了删除对应的 value、然后判断是否存在来触发依赖。

  • has、ownKeys 函数调用较为简单、直接进行依赖收集,等到 set 情况进行触发依赖。

  • collectionHandlers.ts里面的处理比较类似,对相关的操作做出类似的处理,调用原型上面的function(get、set、has)进行原生处理

1.3 effect.ts 解读

  • effect.ts 里面有 track(依赖收集)函数、trigger(依赖触发函数)和 effect 副作用函数。
  • 其中核心的是track和trigger,而effect作为为外部提供依赖监听触发的函数,可以运用在 vue 的更新流程中和日常使用。

1.3.1 effect function 实现

  • (1)处理多层effect包裹 callback;
  • (2)创建reactiveEffect,判断options.lazy是否默认执行一次 effect。
// 定义了effect函数的类型
export interface ReactiveEffect<T = any> {
  (): T; // 一个函数的申明
  _isEffect: true;
  id: number; // 对应自身的id
  active: boolean; // 是否被激活了
  raw: () => T; // 被调用的目标对象源(effect(fn)调用的fn)
  deps: Array<Dep>; // 依赖项(也是一个effect Set集)
  options: ReactiveEffectOptions; // 调用effect的第二个参数选项
  allowRecurse: boolean;
}
// 存储当前调用的effect函数,用于track进行收集
const effectStack: ReactiveEffect[] = [];
// 如其名,激活中的effect函数
let activeEffect: ReactiveEffect | undefined;

export function effect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
  // 传递进来的fn已经是effect过的function,则直接拿这个fn的raw(也就是上次被fn的回调函数)
  if (isEffect(fn)) {
    fn = fn.raw;
  }
  // 调用创建ReactiveEffect
  const effect = createReactiveEffect(fn, options);
  // 默认调用一次
  if (!options.lazy) {
    effect();
  }
  // 返回被创建的ReactiveEffect
  return effect;
}
  • (3)effect 处理的只有创建一个 effect 对象,除了执行,也可以用来暂停处理;
  • (4)同时设置下当前被激活的 effect、用于 track 进行收集,相对简单明了;
  • (5)stop 的作用就是清空依赖、修改active为false
function createReactiveEffect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions
): ReactiveEffect<T> {
  const effect = function reactiveEffect(): unknown {
    // 非激活状态,直接执行fn这个callback,不做后续操作
    // 这种情况下在stop函数被调用时候存在,这时候进行get、has操作则不会收集到对应的依赖
    if (!effect.active) {
      return fn()
    }
    // 不存在情况,需要保存effect到栈里面缓存
    if (!effectStack.includes(effect)) {
      // 清除正在运行的effect依赖
      cleanup(effect)
      try {
        // 禁止收集依赖,收集上次的track state
        enableTracking()
        effectStack.push(effect)
        // 设置当前激活
        activeEffect = effect
        return fn() // 调用fn回调函数,默认收集一次依赖
      } finally {
        effectStack.pop() // 只想完毕推出栈
        // 复原到上次的track state 和 active effect
        resetTracking()
        activeEffect = effectStack[effectStack.length - 1]
      }
    }
  } as ReactiveEffect
  // 配置一些变量到effect fn上做下次处理
  effect.id = uid++
  effect.allowRecurse = !!options.allowRecurse
  effect._isEffect = true
  effect.active = true
  effect.raw = fn
  effect.deps = []
  effect.options = options
  return effect
}

1.3.2 track function 依赖收集实现

  • (1)WeakMapMap 的区别在于,key 必须是对象,而且还是对象的浅引用(删除原对象 key 不受影响);

  • (2)在 Vue3 中、是直接使用 WeakMap 的 key 为被代理前的对象;

    • WeakMap 的 value 则是一个 key 的 Map。
    • key 的 Map 内部又存储了 Set 集合的 ReactiveEffect 函数。
  • (3)track通过判断全局变量shouldTrack 和 activeEffect,进行依赖的 Map 和 Set 的验证,最终存储activeEffect 到 depsMap中对应key: value(Set),然后把 value(Set) 放入 activeEffect.deps 里面

/*
    主要存储形式是 WeakMap<target, new Map<key, new Set<ReactiveEffect>>>
*/
type Dep = Set<ReactiveEffect>;
type KeyToDepMap = Map<any, Dep>;

const targetMap = new WeakMap<any, KeyToDepMap>();
export function track(target: object, type: TrackOpTypes, key: unknown) {
  // 当前状态是禁止收集依赖 | 被激活的activeEffect没有(就是没有调用过effect、ref等API情况)
  if (!shouldTrack || activeEffect === undefined) {
    return;
  }
  // 被代理的key map,也就是依赖收集的map
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }
  // depsMap中拿到Set集,没有也重新设置
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }
  // 当前key不存在这个依赖函数,则添加,并且在依赖函数上面添加这个dep集合,用于后续的清理操作
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect);
    activeEffect.deps.push(dep);
    // 开发模式下调用effect(fn, option的onTrack函数,用于测试和调试)
    if (__DEV__ && activeEffect.options.onTrack) {
      activeEffect.options.onTrack(/* ... */);
    }
  }
}

1.3.3 trigger function 依赖触发实现

  • (1)通过判断什么类型,拿到对应的 effect,放入 Set 集合中,处理一些特殊的 effect;
  • (2)最后 run 函数执行,判断是否有scheduler函数来执行 scheduler 还是 effect 自身。
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) {
    // never been tracked
    return
  }
  // 创建一个依赖函数集,用于收集相关的依赖,等到run时执行触发
  const effects = new Set<ReactiveEffect>()
  const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
    // 收集非当前已经激活的effect
    if (effectsToAdd) {
      effectsToAdd.forEach(effect => {
        if (effect !== activeEffect || effect.allowRecurse) {
          effects.add(effect)
        }
      })
    }
  }
  // 对各自type、Array进行处理添加到effects集合中。
  if (type === TriggerOpTypes.CLEAR) {
    // collection being cleared
    // trigger all effects for target
    depsMap.forEach(add)
  } else if (key === 'length' && isArray(target)) {
    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))
    }
    // 迭代操作的key操作依赖
    // also run for iteration key on ADD | DELETE | Map.SET
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          // 对象在ownKeys情况设置的依赖、add、delete、set情况都有
          add(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // 新增索引到数组之后触发的length改变依赖
          // 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(/* ... */)
    }
    // 最终运行依赖,当依赖存在scheduler函数,则执行提供的scheduler函数,否则执行effect函数
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }
  // 运行需要执行的依赖项
  effects.forEach(run)
}

1.4 ref.ts 解读

1.4.1 ref function 实现

  • (1)首先定义 TypeScript 函数重载,调用createRef
  • (2)在createRef中验证 rawValue 是不是 ref 来进行创建 ref 实例并返回;
export function ref<T extends object>(value: T): ToRef<T>;
export function ref<T>(value: T): Ref<UnwrapRef<T>>;
export function ref<T = any>(): Ref<T | undefined>;
export function ref(value?: unknown) {
  return createRef(value);
}

function createRef(rawValue: unknown, shallow = false) {
  // ref情况直接返回
  if (isRef(rawValue)) {
    return rawValue;
  }
  // 创建一个Ref对象实例
  return new RefImpl(rawValue, shallow);
}
  • (3)先验证是否是 shallow,非 shallow 则进行调用convert深度代理(对象情况)
  • (4)然后在.value获取的时候收集依赖,赋值时调用convert触发依赖
const convert = <T extends unknown>(val: T): T =>
  isObject(val) ? reactive(val) : val;

class RefImpl<T> {
  private _value: T; // 存储我们定义的rawValue

  public readonly __v_isRef = true; // 标识是一个ref

  constructor(private _rawValue: T, public readonly _shallow: boolean) {
    // 判断是不是浅层渲染还是深层次渲染(深层次渲染进行验证、对象情况通过reactive代理)
    this._value = _shallow ? _rawValue : convert(_rawValue);
  }
  // .value时候,触发track操作收集依赖,key为value
  get value() {
    track(toRaw(this), TrackOpTypes.GET, "value");
    return this._value;
  }
  // .set时候验证是否值有变化,并且再次验证是否需要继续代理
  // 最后执行依赖触发操作
  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);
    }
  }
}

1.4.2 toRefs function 实现

  • (1)toRefs方法,把代理过后的对象/数组每一项都进行 toRef 并返回;
  • (2)toRef方法则判断 isRef()来进行 objectRef 转换,旨意在被解构之后还能保留响应式状态。
export function toRefs<T extends object>(object: T): ToRefs<T> {
  if (__DEV__ && !isProxy(object)) {
    console.warn(
      `toRefs() expects a reactive object but received a plain one.`
    );
  }
  const ret: any = isArray(object) ? new Array(object.length) : {};
  // 处理生成新的数值(一个ObjectRef实例)
  // `for in` 这种操作触发了Proxy的`ownKeys`迭代操作
  for (const key in object) {
    ret[key] = toRef(object, key);
  }
  return ret;
}
class ObjectRefImpl<T extends object, K extends keyof T> {
  public readonly __v_isRef = true
  // 保存这个object和对应的key到.value操作上,并且设置成ref情况
  constructor(private readonly _object: T, private readonly _key: K) {}
  get value() {
    return this._object[this._key]
  }

  set value(newVal) {
    this._object[this._key] = newVal
  }
}
export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K
): ToRef<T[K]> {
  // ref情况直接返回,非ref情况则创建ObjectRefImpl
  return isRef(object[key])
    ? object[key]
    : (new ObjectRefImpl(object, key) as any);
}

1.5 computed.ts 解读

computed function 实现

  • (1)判断参数,先处理get、set函数、最后调用new ComputedRefImpl创建实例;

    • 当传递的是 getter,默认给 setter 一个空函数。
    • 当传递的是 options,则直接提供赋值给 getter、setter
  • (2)在 ComputedRefImpl的constructor 时;

    • 创建一个effect、把 getter 当成 callback 传入。
    • 传递 scheduler 函数,调用时通过 trigger 依赖触发。
  • (3)在get时候调用 this.effect 拿到新的值(前提是数据更新了),最终track收集computed的依赖项;

  • (4)则set情况直接调用setter。

export type ComputedGetter<T> = (ctx?: any) => T
export type ComputedSetter<T> = (v: T) => void
export interface WritableComputedOptions<T> {
  get: ComputedGetter<T>
  set: ComputedSetter<T>
}

// 进行函数重载,因为computed的参数可能是一个getter函数,也可以是一个包含get、set的对象
export function computed<T>(getter: ComputedGetter<T>): ComputedRef<T>
export function computed<T>(
  options: WritableComputedOptions<T>
): WritableComputedRef<T>
export function computed<T>(
  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
) {
  let getter: ComputedGetter<T>
  let setter: ComputedSetter<T>
  // 如果是function则是一个get函数
  if (isFunction(getterOrOptions)) {
    getter = getterOrOptions
    setter = __DEV__
      ? () => {
          console.warn('Write operation failed: computed value is readonly')
        }
      : NOOP
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }
  // 创建ComputedRefImpl实例
  // 通过判断是函数/没有options.set函数为只读模式
  return new ComputedRefImpl(
    getter,
    setter,
    isFunction(getterOrOptions) || !getterOrOptions.set
  ) as any
}

class ComputedRefImpl<T> {
  private _value!: T
  private _dirty = true

  public readonly effect: ReactiveEffect<T>

  public readonly __v_isRef = true;
  public readonly [ReactiveFlags.IS_READONLY]: boolean

  constructor(
    getter: ComputedGetter<T>,
    private readonly _setter: ComputedSetter<T>,
    isReadonly: boolean
  ) {
    // 进入创建一个异步执行的effect
    this.effect = effect(getter, {
      lazy: true, // 异步、不立即执行
      scheduler: () => {
        // 等到trigger时候,触发effect.options.scheduler
        if (!this._dirty) {
          // 设置_dirty,依赖被调用过了,这时候才能去获取新的value
          this._dirty = true
          trigger(toRaw(this), TriggerOpTypes.SET, 'value')
        }
      }
    })

    this[ReactiveFlags.IS_READONLY] = isReadonly
  }

  get value() {
    // the computed ref may get wrapped by other proxies e.g. readonly() #3376
    // 等到.value调起的时候拿值
    const self = toRaw(this)
    if (self._dirty) {
      self._value = this.effect() // 调用effect拿到getter执行结果
      self._dirty = false
    }
    // 收集依赖项
    track(self, TrackOpTypes.GET, 'value')
    return self._value
  }

  set value(newValue: T) {
    // 赋值的时,调用setter
    this._setter(newValue)
  }
}

总体流程如图所示

reactivity流程图

总结:

  • reactivity模块核心在于 reactive、track、trigger、effect 四个API、各类操作都离不开。
  • 通过设置对应的 __v_xxx 变量来确定是否是一个 reactive、ref、readonly、raw 等,从而进行各种的处理。
    • isRef、isReactive、isReadonly、toRaw... 都围绕了这些变量。
    • 通过这些变量中的 __v_raw 可以在Proxy代理的get函数里面直接返回对象源。
  • reactive函数 内部处理了ref的解构和赋值,简化使用者使用的难度
  • Vue3的响应式原理就在于对 Proxy的运用和依赖收集触发 操作。
    • 在其他地方通过effect+响应式原理进行更新操作。
    • 具备了组件级的effect可能、当响应式数据发生变化,则调用到effect的callback对内部vnode进行diff。
  • effect原则上就是定义 activeEffect 提供给track进行收集,等到trigger时触发。
  • 而ref的核心是一个class的实例,里面使用了计算属性 get value()、set value(newValue) ,调用了 track、trigger 做到响应 副作用
  • computed函数也是返回一个class实例、在constructor时创建一个effect、设置_dirty来防止重复计算触发getter,当依赖被触发之后才调用get获取新的value
  • 平时项目中,我们可以直接使用vue提供的share模块,直接导出使用相关的工具方法。

参考文献:

  1. vue-next
  2. vue3js.cn
  3. MDN Web Docs