vue响应式变更之路

50 阅读6分钟

以 ref/reactive 为切入点探讨响应式及依赖搜集

预备知识:

  1. vue2 的响应式是通过发布订阅模式完成的,在 get 里面搜集依赖,在 set 里面触发依赖;vue3 的大体模式也差不多
  2. vue2 的依赖搜集是 dep + watcher 作双向依赖搜集完成的;vue3 是建立全局的 WeakMap 结构完成,以劫持监听的 obj 为 keyvalue 是一个 Map 类型,以属性名为 keyvalue 是一个 Set 类型,Set 里面存放的是 effect 函数,effect 就是副作用函数,用来更新

image.png

reactive

// packages\reactivity\src\reactive.ts
export function reactive(target: object) {
  // 调用 createReactiveObject 方法创建 reactive
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  );
}

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  // WeakMap 是类似 map 的数据结构,只能使用对象作为键,不能使用基本数据类型。
  // 并且是弱引用,当只有 WeakMap 引用时,它会被垃圾回收,WeakMap 中对应的条目也会被自动删除
  proxyMap: WeakMap<Target, any> 
) {
  // 看缓存有没有,有直接返回
  const existingProxy = proxyMap.get(target);
  if (existingProxy) {
    return existingProxy;
  }
  // 这里会判断类型,对象和数组会返回1
  const targetType = getTargetType(target);
  if (targetType === 0) {
    return target;
  }
  // 用 Proxy 做数据劫持,会传入 baseHandlers,即上面的 mutableHandlers
  const proxy = new Proxy(
    target,
    targetType === 2 ? collectionHandlers : baseHandlers
  );
  // 缓存
  proxyMap.set(target, proxy);
  return proxy;
}
// packages\reactivity\src\baseHandlers.ts
export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys,
};
const get = /*#__PURE__*/ createGetter(); // 这里先看 get
const set = /*#__PURE__*/ createSetter();

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    // ...省略部分代码
    track(target, TrackOpTypes.GET /** get */, key);

    // 扩展  shallowReactive api 做浅层响应,不会循环处理对象
    if (shallow) {
      return res;
    }

    if (isObject(res)) {
      // 对对象要循环处理
      return isReadonly ? readonly(res) : reactive(res);
    }

    return res;
  };
}
// packages\reactivity\src\effect.ts
export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (shouldTrack && activeEffect) {
    // targetMap 是 WeakMap 结构,以劫持监听的 obj 为 key,value 是一个 Map 类型
    let depsMap = targetMap.get(target);
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()));
    }
    // depsMap 是 Map 结构,以属性名为 key,value 是一个 Set 类型
    let dep = depsMap.get(key);
    if (!dep) {
      depsMap.set(key, (dep = createDep()));
    }

    const eventInfo = __DEV__
      ? { effect: activeEffect, target, type, key }
      : undefined;

    trackEffects(dep, eventInfo);
  }
}
export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  /** 做双向绑定
    * activeEffect 是当前运行的 ReactiveEffect 实例
    * 类似 vue2 中 watcher 和 dep 双向搜集依赖
    * 在这里打印 activeEffect.fn 
    * 控制台显示 componentUpdateFn 函数,正是渲染相关的
    *  if (!instance.isMounted) {
        let vnodeHook;
        const { el, props } = initialVNode;
        const { bm, m, parent } = instance;
        const isAsyncWrapperVNode = isAsyncWrap…
    * 比如 
        const obj = reactive({ name: '张三' });
        const com = computed(() => obj.name);
    * 在页面渲染获取 com 时会触发 obj 的 get,这时候 activeEffect 就是 com
    * activeEffect.fn 就是 () => obj.name
    */
  // todo 这里猜测 ReactiveEffect 有渲染、计算和监听类型
  dep.add(activeEffect!);
  activeEffect!.deps.push(dep);
}

回到 set 方法处理

// packages\reactivity\src\baseHandlers.ts
const set = /*#__PURE__*/ createSetter();
function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    let oldValue = (target as any)[key];
    // 判断是增加属性/值,还是更新
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key);
    // 更新对应的值
    const result = Reflect.set(target, key, value, receiver);
    // 避免原型链属性修改触发
    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;
  };
}
// packages\reactivity\src\effect.ts
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: (Dep | undefined)[] = [];

  // 下面就是根据不同情况,获取要触发更新的 dep
  // schedule runs for SET | ADD | DELETE
  if (key !== void 0) {
    deps.push(depsMap.get(key));
  }

  // 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 changes
        deps.push(depsMap.get("length"));
      }
      break;
    case TriggerOpTypes.SET:
      if (isMap(target)) {
        deps.push(depsMap.get(ITERATE_KEY/** '' */));
      }
      break;
  }

  if (deps.length === 1) {
    if (deps[0]) {
      triggerEffects(deps[0]);
    }
  } else {
    const effects: ReactiveEffect[] = [];
    for (const dep of deps) {
      if (dep) {
        effects.push(...dep);
      }
    }
    // createDep 返回的是一个 Set 集合,做过滤
    triggerEffects(createDep(effects));
  }
}
export function triggerEffects(
  dep: Dep | ReactiveEffect[]
) {
  // spread into array for stabilization
  const effects = isArray(dep) ? dep : [...dep]
  // 先处理计算属性api
  for (const effect of effects) {
    if (effect.computed) {
      triggerEffect(effect)
    }
  }
  for (const effect of effects) {
    if (!effect.computed) {
      triggerEffect(effect)
    }
  }
}
function triggerEffect(
  effect: ReactiveEffect,
) {
  // 执行 effect.run(),触发更新
  // run 就是 执行 fn(),即上面分享的 计算属性内容/渲染函数
  if (effect !== activeEffect || effect.allowRecurse) {
    if (effect.scheduler) {
      effect.scheduler()
    } else {
      effect.run()
    }
  }
}

ref

// packages\reactivity\src\ref.ts
export function ref(value?: unknown) {
  return createRef(value, false)
}
function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}
class RefImpl<T> {
  private _value: T
  private _rawValue: T

  public dep?: Dep = undefined
  public readonly __v_isRef = true

  constructor(value: T, public readonly __v_isShallow: boolean) {
    this._rawValue = __v_isShallow ? value : toRaw(value)
    /**
     * toReactive 判断是否是对象,如果是对象,则使用 reactive,否则直接返回
     * 即对象类型的 ref 会使用 reactive,非对象类型用 class 处理
     * isObject(value) ? reactive(value) : value
     */
    this._value = __v_isShallow ? value : toReactive(value)
  }

  // 对 value 分别做 get/set 方法处理,所以使用时要用 .value 访问。
  get value() {
    trackRefValue(this)
    return this._value
  }

  set value(newVal) {
    const useDirectValue =
      this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
    newVal = useDirectValue ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = useDirectValue ? newVal : toReactive(newVal)
      triggerRefValue(this, newVal)
    }
  }
}

export function trackRefValue(ref: RefBase<any>) {
  if (shouldTrack && activeEffect) {
    ref = toRaw(ref)
    // trackEffects 上面有讲,做了双向依赖收集
    trackEffects(ref.dep || (ref.dep = createDep()))
  }
}

export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
  ref = toRaw(ref)
  if (ref.dep) {
    // triggerEffects 上面有讲,触发对应的依赖
    triggerEffects(ref.dep)
  }
}

reactive 与 ref 区别

  1. 处理的数据类型不同
  2. ref 定义的变量可以做直接赋值全部替换,类似多套了一层 {_value:xxx}
  3. ref 定义的变量,需要通过 .value 访问,reactive 定义的变量直接访问
  4. ref 对普通数据类型做了优化,不用依赖于 proxy

refreactive 都有解构的风险,都可以用 const 处理

tips 直接打印 ref 定义的显示为 Ref 类型;打印 .value 显示为 Reactive 类型

<script setup>
import { ref,reactive } from 'vue'

const msg = ref({
  a:1,
  b:2
})

msg.value = {a:2}  //  "a": 2 }

const msg2 = reactive({
  a:1,
  b:2
})

// 网上 reactive 派一般是怎么处理对象赋值,但是有明显缺陷
Object.assign(msg2,{a:2}) // { "a": 2, "b": 2 }

</script>

<template>
  <h1>{{ msg }}</h1>
  <h1>{{ msg2 }}</h1>
</template>

简易版依赖搜集派发

参考链接:简单的依赖收集和派发更新

v3.2.7 为例

let activeEffect; // 建一个全局变量,用于存储当前正在收集依赖的 effect 函数

// 全局依赖管理
// WeakMap<target, Map<key, Set<effect>>>
const targetMap = new WeakMap();

class ReactiveEffect {
  deps = [];
  /**
   * 扩展
   * constructor(
   *  public fn: () => T
   * ) { }
   * 源代码中怎么写赋值是因为,声明的 public 可以自动赋值同名变量
   */
  constructor(fn) {
    this.fn = fn;
  }
  run() {
    this.fn();
  }
}

// 收集依赖
function track(target, key) {
  let depMap = targetMap.get(target);
  if (!depMap) {
    targetMap.set(target, (depMap = new Map()));
  }
  let dep = depMap.get(key);
  if (!dep) {
    // 这里是用 createDep 生成的 new Set<ReactiveEffect>(effects)
    depMap.set(key, (dep = new Set()));
  }

  if (!dep.has(activeEffect)) {
    dep.add(activeEffect);
    activeEffect.deps.push(dep);
  }
}

// 派发更新
function trigger(target, key) {
  const depMap = targetMap.get(target);
  const dep = depMap.get(key);
  for (const effect of dep) {
    effect.run();
  }
}

/**
 *  创建一个响应式对象
 * @param {Object} obj
 */
function reactive(obj) {
  const proxy = new Proxy(obj, {
    get(target, key) {
      // Reflect 是 es6 引入的一个内置的对象,它提供了一系列的方法来操作对象
      const result = Reflect.get(target, key, proxy);
      track(target, key);
      return result;
    },
    set(target, key, value) {
      const result = Reflect.set(target, key, value, proxy);
      trigger(target, key);
      return result;
    },
  });

  return proxy;
}

/**
 * 定义一个 effect 函数,用于收集依赖
 * @param {function} fn
 */
function effect(fn) {
  activeEffect = new ReactiveEffect(fn);
  fn();
}

// 使用示例
const person = reactive({
  name: "张三",
  age: 18,
});
effect(() => {
  console.log(`person -> ${person.name} -> ${person.age}`);
});
effect(() => {
  console.log(`${person.name} -> ${person.age} -> person`);
});
setTimeout(() => {
  person.name = "李四";
}, 1000);
setTimeout(() => {
  person.age = 20;
}, 2000);

3.4 map 替代 set

简单例子

// 建一个全局变量,用于存储当前正在收集依赖的 effect 函数
let activeEffect; 
// 依赖搜集
const queueEffectSchedulers = [];
let pauseScheduleStack = 0;

class ReactiveEffect {
  deps = [];
  _trackId=0;
  _depsLength=0;
  /**
   * 扩展
   * constructor(
   *  public fn: () => T
   * ) { }
   * 源代码中怎么写赋值是因为,声明的 public 可以自动赋值同名变量
   */
  constructor(fn) {
    this.fn = fn;
  }
  run() {
    this.fn();
  }
}

// 收集依赖
function trackEffect(effect, dep) {
  if (dep.get(effect) !== effect._trackId) {
    dep.set(effect, effect._trackId);
    const oldDep = effect.deps[effect._depsLength];
    if (oldDep !== dep) {
      // 清除旧的依赖
      // if (oldDep) {
      //   cleanupDepEffect(oldDep, effect)
      // }
      effect.deps[effect._depsLength++] = dep;
    } else {
      effect._depsLength++;
    }
  }
}

// 派发更新
function triggerEffects(dep) {
  pauseScheduling();
  for (const effect of dep.keys()) {
    // scheduler 改成 fn,差不多的
    queueEffectSchedulers.push(effect.fn);
  }
  resetScheduling();
}

// 依赖统一触发
function pauseScheduling() {
  pauseScheduleStack++;
}
function resetScheduling() {
  pauseScheduleStack--;
   while (!pauseScheduleStack && queueEffectSchedulers.length) {
    // 3.5 的链式触发也是从未端开始的
    queueEffectSchedulers.shift()();
  }
}

/**
 *  创建一个响应式对象
 * @param {Object} obj
 */
function ref(obj) {
  return new RefImpl(obj);
}
class RefImpl {
  constructor(value) {
    this._value = value;
  }

  get value() {
    trackEffect(activeEffect, (this.dep ??= new Map()));
    return this._value;
  }

  set value(newVal) {
    this._value = newVal;
     triggerEffects(this.dep);
  }
}

/**
 * 定义一个 effect 函数,用于收集依赖
 * @param {function} fn
 */
function effect(fn) {
  activeEffect = new ReactiveEffect(fn);
  fn();
}

// 使用示例
const person = ref(1);
effect(() => {
  console.log(`person.value -> ${person.value}`);
});
setTimeout(() => {
  person.value = "李四";
}, 1000);

3.5 双向链表

参考链接

image.png

// 建一个全局变量,用于存储当前正在收集依赖的 effect 函数
let activeSub;
let batchDepth = 0;
let batchedSub; // 订阅者执行链表表头

class ReactiveEffect {
  deps; // 头部
  depsTail; // 尾部

  constructor(fn) {
    this.fn = fn;
  }
  run() {
    // prepareDeps 把 ink.version = -1
    const prevEffect = activeSub;
    activeSub = this;

    try {
      return this.fn();
    } finally {
      // cleanupDeps(this) 清理依赖
      activeSub = prevEffect; // 恢复到上个订阅者
    }
  }

  notify() {
    batch(this);

    function batch(sub) {
      sub.next = batchedSub;
      batchedSub = sub; // 指向当前订阅者,多个时会形成链表
    }
  }

  trigger() {
    this.run();
  }
}

/**
 * 定义一个 effect 函数,用于收集依赖
 * @param {function} fn
 */
function effect(fn) {
  const e = new ReactiveEffect(fn);
  e.run();
}

/**
 *  创建一个响应式对象
 * @param {Object} obj
 */
function ref(obj) {
  return new RefImpl(obj);
}
class RefImpl {
  dep = new Dep();
  constructor(value) {
    this._value = value;
  }

  get value() {
    this.dep.track();
    return this._value;
  }

  set value(newValue) {
    const oldValue = this._value;
    // 判断两者是否相等 Object.is()与===之间的主要区别在于它们如何处理NaN和-0

    if (!Object.is(oldValue, newValue)) {
      this._value = newValue;
      this.dep.trigger();
    }
  }
}

class Link {
  nextDep; // x轴订阅者依赖链表下一个 link
  prevDep; // x轴订阅者依赖链表上一个 link
  nextSub; // y轴依赖链表下一个 link
  prevSub; // y轴依赖链表上一个 link

  constructor(sub, dep) {
    this.sub = sub;
    this.dep = dep;

    this.version = dep.version;
    this.nextDep =
      this.prevDep =
      this.nextSub =
      this.prevSub =
      this.prevActiveLink =
        undefined;
  }
}

class Dep {
  activeLink = undefined; // 当前 link
  version = 0;
  subs; // 链表尾部指向
  // 依赖搜集
  track() {
    if (!activeSub) return;
    let link = this.activeLink;
    if (link === undefined || link.sub !== activeSub) {
      link = this.activeLink = new Link(activeSub, this);

      if (!activeSub.deps) {
        activeSub.deps = activeSub.depsTail = link;
      } else {
        // 订阅者 link 编织
        // 与订阅者的链表最后一个 link 编织,形成新链表
        link.prevDep = activeSub.depsTail;
        activeSub.depsTail.nextDep = link;
        activeSub.depsTail = link;
      }

      // 依赖 link 编织
      addSub(link);
      function addSub(link) {
        const currentTail = link.dep.subs; // 链表尾部指向

        // 更新链表尾部指向当前 link
        if (currentTail !== link) {
          link.prevSub = currentTail;
          if (currentTail) currentTail.nextSub = link;
        }
        link.dep.subs = link; // 更新依赖的链表尾部指向
      }
    } else if (link.version === -1) {
      // 简易版可以忽略下面的逻辑
      // -1 代表link已经失效,会被清理
      link.version = this.version;

      if (link.nextDep) {
        // 把 link 从原订阅者的链表中移除
        const next = link.nextDep;
        next.prevDep = link.prevDep;
        if (link.prevDep) {
          link.prevDep.nextDep = next;
        }

        // 把 link 放到新订阅者的链表尾部
        link.prevDep = activeSub.depsTail;
        link.nextDep = undefined;
        activeSub.depsTail.nextDep = link;
        activeSub.depsTail = link;

        // 指向新的link链表头部
        if (activeSub.deps === link) {
          activeSub.deps = next;
        }
      }
    }

    return link;
  }
  // 依赖触发
  trigger() {
    this.version++;
    this.notify();
  }
  // 通知订阅者
  notify() {
    startBatch();
    try {
      // 从尾部开始,遍历依赖的 link
      for (let link = this.subs; link; link = link.prevSub) {
        // 触发 effect.notify
        link.sub.notify();
      }
    } finally {
      endBatch();
    }
  }
}

function startBatch() {
  batchDepth++;
}

function endBatch() {
  if (--batchDepth > 0) {
    return;
  }

  while (batchedSub) {
    let e = batchedSub;
    batchedSub = undefined;
    while (e) {
      const next = e.next;
      e.next = undefined;
      e.trigger();
      e = next;
    }
  }
}

// 使用示例
const person = ref(1);
effect(() => {
  console.log(`person.value -> ${person.value}`);
});
setTimeout(() => {
  person.value = "李四";
}, 1000);

求内推

广州/深圳-前端开发