vue3响应式原理:reactive

响应式指的是,当数据发生变化时,视图会跟着变化。以下是以reactive 为例介绍响应式入口、依赖收集和派发更新的实现原理:

js 复制代码
<template>
  <p>{{ reactiveObj.count }}</p>
  <button @click="changeCount">修改数据</button>
</template>
<script setup>
import { reactive } from "vue";
// 通过debugger断点调试响应式整体流程
debugger;
// 定义响应式对象
const reactiveObj = reactive({
  count: 0,
});

// 定义修改数据的方法
const changeCount = function () {
  reactiveObj.count += 1;
};
</script>

以上例子中,定义响应式对象reactiveObj,初次渲染count的值为0。因为是响应式,所以当点击修改数据进行reactiveObj属性count的修改时,会触发试图的更新,数值从012...依次递增。这里开始介绍响应式的机理。

一、响应式入口reactive

js 复制代码
// 全局响应式对象WeakMap
const reactiveMap = /* @__PURE__ */ new WeakMap();

// 判断目标类型
function targetTypeMap(rawType) {
  switch (rawType) {
    case "Object":
    case "Array":
      return 1 /* COMMON */;
    case "Map":
    case "Set":
    case "WeakMap":
    case "WeakSet":
      return 2 /* COLLECTION */;
    default:
      return 0 /* INVALID */;
  }
}
// reactive入口函数
function reactive(target) {
  if (isReadonly(target)) {
    return target;
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  );
}
// 创建响应式对象
function createReactiveObject(
  target,
  isReadonly2,
  baseHandlers,
  collectionHandlers,
  proxyMap
) {
  // 如果不是对象,开发环境直接警告提示
  if (!isObject(target)) {
    if (!!(process.env.NODE_ENV !== "production")) {
      warn(`value cannot be made reactive: ${String(target)}`);
    }
    return target;
  }
  if (target["__v_raw"] && !(isReadonly2 && target["__v_isReactive"])) {
    return target;
  }
  // 在proxyMap中查找是否存在,如果存在直接返回
  const existingProxy = proxyMap.get(target);
  if (existingProxy) {
    return existingProxy;
  }
  // 判断目标类型,本例中为1
  const targetType = getTargetType(target);
  if (targetType === 0 /* INVALID */) {
    return target;
  }
  // 构建代理对象,通过`targetType === 2`确定当前例子中使用的是collectionHandlers作为handler
  const proxy = new Proxy(
    target,
    targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers
  );
  // 在全局proxyMap(即全局的reactiveMap)中记录`target`和`proxy`的映射关系
  proxyMap.set(target, proxy);
  return proxy;
}

通过以上代码可以推断出,例子中的const reactiveObj = reactive({count: 0})执行后最终返回的是proxy代理对象,目标值target{count: 0}handlerbaseHandlers。当进行数据更新时,会触发baseHandlers中的get函数,接下来重点关注下baseHandlers,因为它是get依赖收集和set派发的底层实现。

二、依赖收集和派发更新

js 复制代码
// 定义mutableHandlers(即baseHandlers)
const mutableHandlers = /* @__PURE__ */ new MutableReactiveHandler();
// 定义构造函数MutableReactiveHandler
class MutableReactiveHandler extends BaseReactiveHandler {
  constructor(isShallow2 = false) {
    super(false, isShallow2);
  }
  // 重点关注set函数,这里实现了派发更新
  set(target, key, value, receiver) {
    let oldValue = target[key];
    if (!this._isShallow) {
      const isOldValueReadonly = isReadonly(oldValue);
      if (!isShallow(value) && !isReadonly(value)) {
        oldValue = toRaw(oldValue);
        value = toRaw(value);
      }
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        if (isOldValueReadonly) {
          return false;
        } else {
          oldValue.value = value;
          return true;
        }
      }
    }
    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, "add", key, value);
      } else if (hasChanged(value, oldValue)) {
        // 派发更新
        trigger(target, "set", key, value, oldValue);
      }
    }
    return result;
  }
  // deleteProperty(target, key) {}
  // has(target, key) {}
  // ownKeys(target) { }
}
// 定义BaseReactiveHandler
class BaseReactiveHandler {
  constructor(_isReadonly = false, _isShallow = false) {
    this._isReadonly = _isReadonly;
    this._isShallow = _isShallow;
  }
  // 重点关注get函数,这里实现了依赖收集
  get(target, key, receiver) {
    const isReadonly2 = this._isReadonly,
      isShallow2 = this._isShallow;
    if (key === "__v_isReactive") {
      return !isReadonly2;
    } else if (key === "__v_isReadonly") {
      return isReadonly2;
    } else if (key === "__v_isShallow") {
      return isShallow2;
    } else if (key === "__v_raw") {
      if (
        receiver ===
          (isReadonly2
            ? isShallow2
              ? shallowReadonlyMap
              : readonlyMap
            : isShallow2
            ? shallowReactiveMap
            : reactiveMap
          ).get(target) || // receiver is not the reactive proxy, but has the same prototype
        // this means the reciever is a user proxy of the reactive proxy
        Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
      ) {
        return target;
      }
      return;
    }
    const targetIsArray = isArray(target);
    if (!isReadonly2) {
      if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
        return Reflect.get(arrayInstrumentations, key, receiver);
      }
      if (key === "hasOwnProperty") {
        return hasOwnProperty;
      }
    }
    // 获取实际的值
    const res = Reflect.get(target, key, receiver);
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res;
    }
    if (!isReadonly2) {
      // 依赖收集
      track(target, "get", key);
    }
    if (isShallow2) {
      return res;
    }
    if (isRef(res)) {
      return targetIsArray && isIntegerKey(key) ? res : res.value;
    }
    if (isObject(res)) {
      return isReadonly2 ? readonly(res) : reactive(res);
    }
    return res;
  }
}

1、依赖收集

js 复制代码
// 定义全局的依赖收集WeapMap数据
const targetMap = /* @__PURE__ */ new WeakMap();
// createDep函数
const createDep = (cleanup, computed) => {
  const dep = /* @__PURE__ */ new Map();
  dep.cleanup = cleanup;
  dep.computed = computed;
  return dep;
};
// 依赖收集入口
function track(target, type, key) {
  if (shouldTrack && activeEffect) {
    // 在全局targetMap中获取target
    let depsMap = targetMap.get(target);
    if (!depsMap) {
      // 不存在时,全局定义空的Map数据depsMap
      targetMap.set(target, (depsMap = /* @__PURE__ */ new Map()));
    }
    // 在depsMap查找key,本例中为count
    let dep = depsMap.get(key);
    if (!dep) {
      // 不存在时通过设置以count为key的dep
      depsMap.set(key, (dep = createDep(() => depsMap.delete(key))));
    }
    // 通过trackEffect的方式跟踪当前activeEffect和dep之间的关系
    trackEffect(
      activeEffect,
      dep,
      !!(process.env.NODE_ENV !== "production")
        ? {
            target,
            type,
            key,
          }
        : void 0
    );
  }
}
// 定义trackEffect
function trackEffect(effect2, dep, debuggerEventExtraInfo) {
  var _a;
  // 如果当前dep中effect2(即activeEffect)的映射值!== effect2._trackId
  if (dep.get(effect2) !== effect2._trackId) {
    // 则为dep设置effect2和effect2._trackId的映射关系
    dep.set(effect2, effect2._trackId);
    const oldDep = effect2.deps[effect2._depsLength];
    if (oldDep !== dep) {
      if (oldDep) {
        cleanupDepEffect(oldDep, effect2);
      }
      // 这里为deps数组添加dep;
      effect2.deps[effect2._depsLength++] = dep;
    } else {
      effect2._depsLength++;
    }
    if (!!(process.env.NODE_ENV !== "production")) {
      (_a = effect2.onTrack) == null
        ? void 0
        : _a.call(effect2, extend({ effect: effect2 }, debuggerEventExtraInfo));
    }
  }
}

以上逻辑中通过trackEffect来构建当前count和正在执行的activeEffect试图渲染函数之间的依赖关系,activeEffectdeps,不仅收集count,还收集其他可能变化的响应式数据。当数据发生变化时,好通过派发更新的方式,来重新渲染当前的试图。

2、派发更新

js 复制代码
function trigger(target, type, key, newValue, oldValue, oldTarget) {
  const depsMap = targetMap.get(target);
  if (!depsMap) {
    return;
  }
  // 定义依赖数组
  let deps = [];
  if (type === "clear") {
    deps = [...depsMap.values()];
  } else if (key === "length" && isArray(target)) {
    const newLength = Number(newValue);
    depsMap.forEach((dep, key2) => {
      if (key2 === "length" || (!isSymbol(key2) && key2 >= newLength)) {
        deps.push(dep);
      }
    });
  } else {
    if (key !== void 0) {
      // 将当前获取到的key对应的依赖推入到deps中
      deps.push(depsMap.get(key));
    }
    switch (type) {
      case "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)) {
          deps.push(depsMap.get("length"));
        }
        break;
      case "delete":
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY));
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
          }
        }
        break;
      case "set":
        if (isMap(target)) {
          deps.push(depsMap.get(ITERATE_KEY));
        }
        break;
    }
  }
  // 暂停vue调度过程
  pauseScheduling();
  // 遍历deps中的各依赖
  for (const dep of deps) {
    if (dep) {
      // 定义triggerEffects
      triggerEffects(
        dep,
        4,
        !!(process.env.NODE_ENV !== "production")
          ? {
              target,
              type,
              key,
              newValue,
              oldValue,
              oldTarget,
            }
          : void 0
      );
    }
  }
  // 重置vue调度过程
  resetScheduling();
}
// 定义队列中可执行的渲染计划
const queueEffectSchedulers = [];
function triggerEffects(dep, dirtyLevel, debuggerEventExtraInfo) {
  var _a;
  // 暂停vue调度过程
  pauseScheduling();

  for (const effect2 of dep.keys()) {
    let tracking;
    if (
      effect2._dirtyLevel < dirtyLevel &&
      (tracking != null
        ? tracking
        : (tracking = dep.get(effect2) === effect2._trackId))
    ) {
      effect2._shouldSchedule ||
        (effect2._shouldSchedule = effect2._dirtyLevel === 0);
      effect2._dirtyLevel = dirtyLevel;
    }
    if (
      effect2._shouldSchedule &&
      (tracking != null
        ? tracking
        : (tracking = dep.get(effect2) === effect2._trackId))
    ) {
      // 当前视图派发更新
      effect2.trigger();
      if (
        (!effect2._runnings || effect2.allowRecurse) &&
        effect2._dirtyLevel !== 2
      ) {
        // 当前affect已确认要更新时,设置为false,防止重复更新
        effect2._shouldSchedule = false;
        if (effect2.scheduler) {
          // 队列中可执行的渲染计划推入视图更新的执行计划
          queueEffectSchedulers.push(effect2.scheduler);
        }
      }
    }
  }
  // 重置vue调度过程
  resetScheduling();
}

这里,在重置vue调度过程的时候,执行以下逻辑

js 复制代码
function resetScheduling() {
  pauseScheduleStack--;
  // 当pauseScheduleStack为0,并且queueEffectSchedulers不为空时
  while (!pauseScheduleStack && queueEffectSchedulers.length) {
    // 在队列中以先入先出的方式,执行渲染函数
    queueEffectSchedulers.shift()();
  }
}

事件执行的其实是其内部的queueJob方法:

js 复制代码
// 定义队列事件执行
function queueJob(job) {
  if (
    !queue.length ||
    !queue.includes(
      job,
      isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
    )
  ) {
    if (job.id == null) {
      queue.push(job);
    } else {
      queue.splice(findInsertionIndex(job.id), 0, job);
    }
    queueFlush();
  }
}
// 定义队列流程执行
function queueFlush() {
  if (!isFlushing && !isFlushPending) {
    // 开启流程挂起
    isFlushPending = true;
    // 在下一个异步队列中执行flushJobs,进而执行重新渲染
    currentFlushPromise = resolvedPromise.then(flushJobs);
  }
}

以上可以看出,数据变化时,最终执行渲染逻辑是在下一个异步任务currentFlushPromise = resolvedPromise.then(flushJobs)

相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax