响应式指的是,当数据发生变化时,视图会跟着变化。以下是以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
的修改时,会触发试图的更新,数值从0
到1
、2
、...
依次递增。这里开始介绍响应式的机理。
一、响应式入口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}
,handler
是baseHandlers
。当进行数据更新时,会触发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
试图渲染函数之间的依赖关系,activeEffect
有deps
,不仅收集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)
。