reactive
是一个特别常用的API
,在Vue3
中,reactive
函数是用来创建一个响应式对象。底层用到了哪些技术呢?我们一起来看一下。 学完本文,你将获得以下知识:
reactive
函数底层实现原理reactive
函数与单例模式之间的关系reactive
函数与代理模式之间的关系reactive
函数中类的继承关系reactive
函数与发布订阅者模式之间的关系
ts
<body>
<div id="app"></div>
<script>
const App = {
template:`
{{ reactiveObj.count }}
<button @click="changeCount">修改数据</button>
`,
setup() {
debugger;
// 定义响应式对象
const reactiveObj = Vue.reactive({
count: 1
})
// 定义修改数据的方法
const changeCount = function() {
reactiveObj.count += 1;
}
// 暴露给模板
return {
reactiveObj,
changeCount
};
}
};
// 创建应用并挂载
const app = Vue.createApp(App);
app.mount("#app");
</script>
</body>
以上例子中,我们定义了一个响应式对象reactiveobject
,然后通过changeCount
方法修改了reactiveObj
中的count
属性,此时,页面中的数据也会随之更新。下面我们来看一下reactive
函数是如何实现的。
ts
// reactive函数
function reactive(target) {
if (isReadonly(target)) {
return target;
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
);
}
// createReactiveObject函数
function createReactiveObject(
target,
isReadonly2,
baseHandlers,
collectionHandlers,
proxyMap
) {
// 如果不是一个对象的话,直接返回。
const targetType = getTargetType(target);
if (targetType === 0) {
return target;
}
// 如果在proxyMap中能找到缓存的话,就直接返回。
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
// 代理模式:在访问target之前先访问collectionHandlers
const proxy = new Proxy(
target,
targetType === 2 ? collectionHandlers : baseHandlers
);
proxyMap.set(target, proxy);
return proxy;
}
一、单例模式
单例模式指的是全局对象中有且只有一个实例对象,当前例子:
ts
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
以上如果existingProxy
存在,直接返回,否则才会执行后续逻辑,确保了target
有且只有一个代理对象。
接下来看代理模式。
二、代理模式
以上例子中代码为:
ts
// 这里targetType为1,执行的是baseHandlers
const proxy = new Proxy(
target,
targetType === 2 ? collectionHandlers : baseHandlers
);
我们知道代理模式指的是在访问目标对象之前先访问代理对象,那么在reactive
函数中,target
就是reactiveObj
,那么在访问reactiveObj
之前,会先访问baseHandlers
函数,继续看baseHandlers
函数中用到的继承关系。
三、类的继承
baseHandlers
来源于开头例子中的mutableHandlers
:
ts
// 处理函数实例
const mutableHandlers = new MutableReactiveHandler();
// MutableReactiveHandler类
class MutableReactiveHandler extends BaseReactiveHandler {
constructor(isShallow2 = false) {
super(false, isShallow2);
}
set(target, key, value, receiver) {
let oldValue = target[key];
// ...
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key);
const result = Reflect.set(
target,
key,
value,
isRef(target) ? target : receiver
);
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, "add", key, value);
} else if (hasChanged(value, oldValue)) {
// 在修改数据时,触发set函数,触发试图更新
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(target, key, receiver) {
// 省略其他支线逻辑
const isReadonly2 = this._isReadonly,
isShallow2 = this._isShallow;
if (!isReadonly2) {
track(target, "get", key);
}
return res;
}
}
从以上例子中,可以看出,mutableHandlers
是MutableReactiveHandler
的实例,而MutableReactiveHandler
继承了BaseReactiveHandler
,在BaseReactiveHandler
中,定义了get
方法,该方法中调用了track
方法,该方法用于收集依赖。在MutableReactiveHandler
中,定义了set
方法,该方法中调用了trigger
方法,该方法用于触发更新。
接下来,我们看一下在track
中发布者是如何收集订阅者的,在trigger
中发布者又是如何触发订阅者更新视图的。
四、发布订阅者模式
1、Dep
类
再构建vnode
时,会访问到reactiveObj
中的count
属性。先触发get
函数,接着会触发track
函数:
ts
// 全局对象targetMap
const targetMap = /* @__PURE__ */ new WeakMap();
// track函数
function track(target, type, key) {
if (shouldTrack && activeSub) {
// 获取targetMap
let depsMap = targetMap.get(target);
// 如果depsMap不存在,以target为key,以创建一个Map对象为value,创建一个target和depsMap的映射关系
if (!depsMap) {
targetMap.set(target, (depsMap = /* @__PURE__ */ new Map()));
}
// 在depsMap中获取key对应的dep。
let dep = depsMap.get(key);
// 如果dep不存在,以当前key值为key,创建它与Dep实例之间的映射关系
if (!dep) {
depsMap.set(key, (dep = new Dep()));
dep.map = depsMap;
dep.key = key;
}
{
dep.track({
target,
type,
key,
});
}
}
}
从targetMap
中可以通过target
(即ReactiveObj
)获取到depsMap
,在depsMap
中可以通过key
(即count
)获取到dep
,接下来看 dep
对应的Dep
类的实现。
ts
// Dep类,作为发布者和订阅者之间的关系管理类
class Dep {
constructor(computed) {
this.computed = computed;
this.version = 0;
// 核心: dep和当前活跃的sub之间的关系
this.activeLink = void 0;
this.subs = void 0;
this.map = void 0;
this.key = void 0;
// 订阅者个数
this.sc = 0;
this.__v_skip = true;
{
this.subsHead = void 0;
}
}
track(debugInfo) {
// 依赖收集,省略逻辑...
}
trigger(debugInfo) {
// 派发更新,省略逻辑...
}
notify(debugInfo) {
// 通知更新,省略逻辑...
}
}
至此,从reactiveObj
开始,就找到了最终控制发布订阅者关系的Dep
类。
这里我们先看看控制视图的订阅者对应的ReactiveEffect
类。
2、ReactiveEffect
类
ReactiveEffect
的实例化对象就是一个订阅者。渲染过程的调度过程就是由其实例化对象中的run
方法进行的,核心代码如下:
ts
// ReactiveEffect类
class ReactiveEffect {
constructor(fn) {
this.fn = fn;
this.deps = void 0;
this.depsTail = void 0;
this.flags = 1 | 4;
this.next = void 0;
this.cleanup = void 0;
this.scheduler = void 0;
if (activeEffectScope && activeEffectScope.active) {
activeEffectScope.effects.push(this);
}
}
pause() {
// 省略逻辑...
}
resume() {
// 省略逻辑...
}
notify() {
// 省略逻辑...
}
run() {
// 当前活跃的订阅者就是this,即effect
activeSub = this;
try {
return this.fn();
} finally {
// 省略逻辑...
}
}
stop() {
// 省略逻辑...
}
trigger() {
// 省略逻辑...
}
runIfDirty() {
// 省略逻辑...
}
get dirty() {
// 省略逻辑...
}
}
在上述对象中,this.fn=fn
,实例化时指向的是实例化的执行函数componentUpdateFn
,获取vnode
和patch
的逻辑都在其中。const update = instance.update = effect.run.bind(effect)
,执行update
时,实际执行的是ReactiveEffect
的run
方法。此时,就将当前实例赋值给了activeSub
。
说明白了Dep
和 ReactiveEffect
的作用,再来看看订阅者是如何和发布者发生关联的。
3、构建关系(时机:首次渲染)
在首次渲染生成vnode
时,会访问到reactiveObj
中的count
,进而执行到BaseReactiveHandler
类中的get
函数。该函数调用track(target, "get", key)
,即dep.track({ target, type, key });
进行发布者和订阅者的关系构建,核心代码如下:
ts
// this.dep.track
track(debugInfo) {
if (!activeSub || !shouldTrack || activeSub === this.computed) {
return;
}
let link = this.activeLink;
if (link === void 0 || link.sub !== activeSub) {
// 建立activeSub和dep的关系
link = this.activeLink = new Link(activeSub, this);
if (!activeSub.deps) {
// 订阅者的依赖deps指向link
activeSub.deps = activeSub.depsTail = link;
} else {
// 链表的形式可以管理多个订阅者
link.prevDep = activeSub.depsTail;
activeSub.depsTail.nextDep = link;
activeSub.depsTail = link;
}
addSub(link);
}
return link;
}
// Link类的实例将包含sub和dep两个属性,分别指向依赖和订阅者
class Link {
constructor(sub, dep) {
this.sub = sub;
this.dep = dep;
this.version = dep.version;
// 链表的形式管理dep和sub
this.nextDep = this.prevDep = this.nextSub = this.prevSub = this.prevActiveLink = void 0;
}
}
// addSub函数
function addSub(link) {
link.dep.sc++;
if (link.sub.flags & 4) {
if (link.dep.subsHead === void 0) {
// link.dep.subsHead作为链表头,起初也指向link
link.dep.subsHead = link;
}
// 依赖的订阅者subs也指向link
link.dep.subs = link;
}
}
这里需要重点关注的是activeSub.deps = activeSub.depsTail = link
和link.dep.subs = link
,两者都指向了同一个link
对象,该对象包含dep
和sub
属性,这样activeSub
和dep
之间建立了关系。实现了你中有我, 我中有你
的双向依赖关系。从而数据变化时的找到对应的订阅者,进入触发更新的流程。
4、触发更新(时机:数据修改)
当执行reactiveObj.count += 1;
操作时,会触发trigger(target, "set", key, value, oldValue);
函数,进而执行到this.dep.trigger
函数,派发更新的核心代码如下:
依赖MutableReactiveHandler
中的set
逻辑,进而执行其中的trigger
逻辑:
ts
// MutableReactiveHandler的trigger函数
trigger(target, "set", key, value, oldValue);
// 以下函数中省略所有支线逻辑,如需要请查阅源码
function trigger(target, type, key, newValue, oldValue, oldTarget) {
const depsMap = targetMap.get(target);
const run = (dep) => {
if (dep) {
{
// 执行dep中的trigger函数
dep.trigger({
target,
type,
key,
newValue,
oldValue,
oldTarget,
});
}
}
};
if (key !== void 0 || depsMap.has(void 0)) {
// 开始执行run函数
run(depsMap.get(key));
}
}
其中通过depsMap = targetMap.get(target)
获得了desMap
,即实例化后的dep
,我们接着看其中的trigger
函数:
ts
// Dep类中的trigger函数
trigger(debugInfo) {
this.version++;
globalVersion++;
this.notify(debugInfo);
}
// Dep的notify函数
notify() {
for (let link = this.subs; link; link = link.prevSub) {
if (link.sub.notify()) {
;
link.sub.dep.notify();
}
}
}
当执行到ink.sub.notify()
时,即执行到了订阅者类ReactiveEffect
的notify
函数,核心代码如下:
ts
// sub的notify函数
notify() {
if (this.flags & 2 && !(this.flags & 32)) {
return;
}
if (!(this.flags & 8)) {
batch(this);
}
}
// batch函数
function batch(sub, isComputed = false) {
sub.flags |= 8;
if (isComputed) {
sub.next = batchedComputed;
batchedComputed = sub;
return;
}
sub.next = batchedSub;
// batchedSub指向第一个sub
batchedSub = sub;
}
当以上逻辑结束时,继续回到Dep
的endBatch
方法:
ts
// dep的endBatch方法
function endBatch() {
if (--batchDepth > 0) {
return;
}
// 省略计算属性相关的逻辑...
while (batchedSub) {
let e = batchedSub;
batchedSub = void 0;
while (e) {
const next = e.next;
e.next = void 0;
e.flags &= -9;
if (e.flags & 1) {
try {
// 执行e(batchedSub)的trigger()方法
e.trigger();
} catch (err) {
if (!error) error = err;
}
}
e = next;
}
}
if (error) throw error;
}
// sub的trigger()方法
trigger() {
if (this.flags & 64) {
pausedQueueEffects.add(this);
} else if (this.scheduler) {
// 即effect.scheduler = () => queueJob(job);
this.scheduler();
} else {
this.runIfDirty();
}
}
再看 queueJob
的核心逻辑
ts
function queueJob(job) {
if (!(job.flags & 1)) {
const jobId = getId(job);
const lastJob = queue[queue.length - 1];
if (!lastJob || (!(job.flags & 2) && jobId >= getId(lastJob))) {
// 将当前任务插入到数组尾部
queue.push(job);
} else {
// 根据jobId,将其移动到合适的位置
queue.splice(findInsertionIndex(jobId), 0, job);
}
job.flags |= 1;
queueFlush();
}
}
// queueFlush
function queueFlush() {
if (!currentFlushPromise) {
// flushJobs是异步任务,得等下个异步队列才执行
currentFlushPromise = resolvedPromise.then(flushJobs);
}
}
下一个异步队列,执行的任务:
ts
function flushJobs(seen) {
{
seen = seen || /* @__PURE__ */ new Map();
}
const check = (job) => checkRecursiveUpdates(seen, job);
try {
for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
const job = queue[flushIndex];
if (job && !(job.flags & 8)) {
if (check(job)) {
continue;
}
if (job.flags & 4) {
job.flags &= ~1;
}
// 在错误处理函数中执行job
callWithErrorHandling(job, job.i, job.i ? 15 : 14);
if (!(job.flags & 4)) {
job.flags &= ~1;
}
}
}
} finally {
for (; flushIndex < queue.length; flushIndex++) {
const job = queue[flushIndex];
if (job) {
job.flags &= -2;
}
}
flushIndex = -1;
queue.length = 0;
flushPostFlushCbs(seen);
currentFlushPromise = null;
if (queue.length || pendingPostFlushCbs.length) {
flushJobs(seen);
}
}
}
// 错误处理函数
function callWithErrorHandling(fn, instance, type, args) {
try {
return args ? fn(...args) : fn();
} catch (err) {
handleError(err, instance, type);
}
}
// 因为job = instance.job = effect.runIfDirty.bind(effect);,所以,fn就是runIfDirty函数
runIfDirty() {
if (isDirty(this)) {
// 这里就是最终的渲染逻辑
this.run();
}
}
以上,就是从数据reactiveObj.count
变化,到订阅者执行渲染逻辑的全过程。
总结:
reactive
函数会返回一个代理对象proxy
,在生成vnode
的过程中需要访问到reactiveObj.count
,首先会访问到过起代理对象中的执行逻辑baseHandlers
。baseHandlers
中的基类和继承类分别包含set
和get
函数,访问时数据是触发get
中的track
函数,进行依赖收集;在数据改变时,会让订阅者执行link.sub.notify()
的操作,从而执行数据变化后的最终渲染函数。