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)

相关推荐
joan_851 分钟前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
还是大剑师兰特24 分钟前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
m0_7482361132 分钟前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
Watermelo6171 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_748248941 小时前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5
m0_748235611 小时前
从零开始学前端之HTML(三)
前端·html
一个处女座的程序猿O(∩_∩)O3 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink6 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者7 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-8 小时前
验证码机制
前端·后端