vue3 响应式原理

103 阅读5分钟

响应式

对数据的操作进行拦截处理。当访问它时就对当前触发它的渲染副作用函数进行收集,这样之后在更改时就可以通知对应的副作用函数进行更新。进而实现了响应式的效果

reactive 函数

  • reactive()-> createReactiveObject() 对 target 进行 new Proxy(target, mutableHandlers) 拦截并保存结果 p 防止重复响应式化。
  • mutableHandlers{} 主要对 get/set/deleteProperty/has/ownKeys 操作进行拦截
    • getter() -> createGetter() 配合 const res = Reflect.get(target, key, receiver) 获取值,并执行对当前 key 的依赖收集 track()如果返回的 res 也是对象,则继续递归进行响应式化
      • 区别:与vue2不同的是 reactive() 一开始只对对象本身进行 proxy 处理,而子层只有在 getter 获取时,判断为对象再进行递归处理。这样降低了消耗和对无效数据的响应式处理
    • setter() -> createSetter() 配合 const result = Reflect.set(target, key, value, receiver) 并触发当前 key 的派发通知 trigger()。
  • track() 收集的是响应式 effect() 副作用函数(vue2 收集的是 watcher{})。全局 targetMap{} 存储着每个 target 对应的 depsMap{},depsMap{} 内为该 target 的 key 对应的 dep[ ...effect() ] 集合。同时当前的 activeEffect=effect() 也会双向收集 dep[],用于后续的 cleanUp() 清理
    • 组件初始化时 setupRenderEffect() 设置并执行 effect() -> renderComponentRoot() 创建 subtree 会去读对应的 data 进而完成当前的 effect() 被收集
    • 结构关系:targetMap: { target: { key: [ effect() ] } };targetMap 为 WeakMap 类型
  • trigger() 派发时通过 target->key->dep[] 获取并创建个新的 effects[] 用于存储待执行的 effect()
  • effect() 副作用函数 -> createReactiveEffect() 是对当前待执行渲染函数进行包裹,内部保存了这个 fn() 并定义其他属性。当需要执行时配合 effectStack[] 调用栈、activeEffect 指针实现树深度遍历式的触发顺序,并完成当前 effeect() 被收集的动作
    • cleanup() 在执行时先清除收集了该 effect() 的 dep,之后实际运行时在重新收集。防止已经失效的 dep 对 effect() 的重新触发

ref 函数

  • ref() -> createRef() -> new RefImpl() 生成 ref{}
  • class RefImpl{} 类内部定义了对 value 属性且对 getter/setter 访问拦截。这就是为什么需要以 ref.value 形式读取/修改了。且 getter/setter 拦截对应着 track/trigger()
    • track() 收集的 target 是生成的 new RefImpl() 对象本身
    • 注意:当创建的 value 是对象/数组类型,则调用的 reactive()

计算属性 computed

对配置的 getter() 进行延迟处理,及根据依赖的变动再重新计算获取新值

  • computed() -> new ComputedRefImpl() 生成 computed{},对参数做标准化处理
  • class ComputedRefImpl{} 执行时对 getter() 进行 effect 包裹并设置 { lazy, scheduler } 所以这个 effect() 非立即执行,之后当读取该属性时才真正执行 effect() -> getter() 获取返回值同时保存它。然后在执行内部 track() 收集依赖
    • 依赖收集流程:因为模版先读取 computed 而后 computed 触发 getter(),然后 computed 再执行自身的 track(),所以 computed 收集的是 render.effect() 而依赖收集的 computed.effect()
    • 更新流程:修改依赖 data 它通知 computed.effect(),然后 computed 再通知 render.effect() 读取新值
    • 区别:在 vue2 内 data 是同时收集的 computed.watcher{} 和 render.watcher{},然后修改时先告诉 computed 重新计算,再触发 render()

侦听器 watch

对 getter() 进行标准化处理,对 cb 进行 scheduler 化并按 flush 放入不同的执行队列中

  • watch/watchEffect() -> doWatch() 主要实习内容
    • getter() 会对传入的数据进行标准化处理
    • 构造 job=applyCb() -> cb() 回调,实现 watch(newVal, oldVal, onCleanup) 传递参数
    • 对 cb() 进行 scheduler 化处理。按配置的 flush 方式,将 cb() 放入不同的异步队列内 pendingPreFlushCbs/pendingPostFlushCbs[]。 而 sync 则不进入异步队列直接是 scheduler=job
      • 所以非 sync 时多次修改响应式数据在一次 tick 内 cb() 只触发一次
    • 创建 effect() -> getter() 会在 job() 内被执行
  • flushJobs() 内实际触发等待队列内的 cb()
    • 在组件更新前执行 flushPreFlushCbs() -> pendingPreFlushCbs[],在更新后执行 flushPostFlushCbs() -> pendingPostFlushCbs[]
  • 区别:3.x 同一个 getter 不再支持配置多个 cb(),需要添加多个 wetch() 实现

简单实现

响应式

  • 依赖收集的层位变为 WeakMap -> Map -> Set
    • wm 对应整个对象的 new Observe
    • map 是整个 data 和 key 之间的关联桥梁
    • set 是对应 key 的收集 dep
let activeEffect = null;
const effectStack = [];
function effect(fn) {
  const effectFn = () => {
    // 清除旧的依赖
    cleanup(effectFn);
    effectStack.push(effectFn);
  	activeEffect = effectFn;
    // activeEffect = effectFn;
    fn();
    effectStack.pop();
  	activeEffect = effectStack[effectStack.length - 1];
  };
  effectFn.deps = [];
  effectFn();
}

function cleanup(effectFn) {
  for (let i = 0; i < effectFn.deps.length; i++) {
    // 清除收集了 effectFn 的 deps
    const deps = effectFn.deps[i];
    deps.delete(effectFn);
  }
  // 清除 effectFn 收集的 deps
  effectFn.deps.length = 0;
}

// 层级关系
// wm -> map -> set
const reactiveMap = new WeakMap();
const obj = new Proxy(data, {
  get(target, key) {
    // map
    let depsMap = reactiveMap.get(target);
    if (!depsMap) {
      reactiveMap.set(target, (depsMap = new Map()));
    }
    // set
    let deps = depsMap.get(key);
    if (!deps) {
      depsMap.set(key, (deps = new Set()));
    }
    // 收集依赖
    deps.add(activeEffect);
    // 相互收集依赖
    activeEffect.deps.push(deps);

    return target[key];
  },
  set(target, key, newValue) {
    target[key] = newValue;

    const depsMap = reactiveMap.get(target);
    if (!depsMap) return;
    // 获取对应的 set
    const effects = depsMap.get(key);
    // console.log('## set effects', effects);
    // 通知该 deps 下的程序
    // effects && effects.forEach((fn) => fn());
    // 生成新的 effects 对象用于执行函数与原有 effects 脱离,防止死循环
    // 不该动会先 del 再 add 这样循环的,因为都操作的一个 effects 对象
    const effectsToRun = new Set(effects);
    effectsToRun.forEach((fn) => fn());
  }
});

computed

  • 同样通过 dirty/lazy 来控制 computed 的执行时机
const data = {
  a: 1,
  b: 2
};

function cleanup(effectFn) {
  for (let i = 0; i < effectFn.deps.length; i++) {
    const deps = effectFn.deps[i];
    deps.delete(effectFn);
  }
  effectFn.deps.length = 0;
}
function pushTarget(effectFn) {
  effectStack.push(effectFn);
  activeEffect = effectFn;
}
function popTarget() {
  effectStack.pop();
  activeEffect = effectStack[effectStack.length - 1];
}

let activeEffect;
const effectStack = [];
function effect(fn, options = {}) {
  const effectFn = () => {
    cleanup(effectFn);
    pushTarget(effectFn);
    // 获取返回值
    const res = fn();
    popTarget();

    return res;
  };
  effectFn.deps = [];
  options.options = options;

  if (!options.lazy) {
    effectFn();
  }
  // 让执行动作由外部控制
  return effectFn;
}

// 计算属性主体。读取方式 xxx.value
// 让 computed 也变为计算属性
function computed(fn) {
  // value 作为缓存
  let value;
  let dirty = true;
  const effectFn = effect(fn, {
    lazy: true,
    scheduler(fn) {
      // 自己来触发自己的 
      // 更改 drity 状态,后续 xxx.value 时获取最新结果
      dirty = true;
      // 添加响应式 触发
      trigger(obj, 'value');
    }
  });
  // 创建一个 value 的拦截函数
  // 最后这个 obj 也成为响应式
  const obj = {
    get value() {
      if (dirty) {
        value = effectFn();
        dirty = false;
      }
      // 添加响应式 收集
      track(obj, 'value');
      return value;
    }
  };
  // 通过 xxx.value 获取内容
  return obj;
}

const obj = new Proxy(data, {
  get(target, key) {
    track(target, key);

    return target[key];
  },
  set(target, key, newVal) {
    target[key] = newVal;

    trigger(target, key);
  }
});

// 响应式数据记录
const reactiveMap = new WeakMap();
function track(target, key) {
  let depsMap = reactiveMap.get(target);
  if (!depsMap) {
    reactiveMap.set(target, (depsMap = new Map()));
  }

  let deps = depsMap.get(key);
  if (!deps) {
    deps.set(key, (deps = new Set()));
  }

  deps.add(activeEffect);
  activeEffect.deps.push(deps);
}
function trigger(target, key) {
  const depsMap = reactiveMap.get(target);
  if (!depsMap) return;

  const effects = depsMap.get(key);
  const effectsToRun = new Set(effects);
  // effectsToRun.forEach((fn) => fn());
  effectsToRun.forEach((effectFn) => {
    // 让 scheduler 回调函数去执行 effect
    // 既 data 更新时并不直接执行 effect,而是修改 c-w.dirty 然后让 r-w 去获取新的
    if (effectFn.options.scheduler) {
      effectFn.options.scheduler(effectFn);
    } else {
      effectFn();
    }
  });
}