vue3响应式原理:被监听的watch
侦听器指的是,数据发生变化时,会触发被监听的事件(比如:更改 Dom,修改另一些数据等)。以下是watch
相关的实现原理:
先看个监听ref类型
数据变化的例子:
<template>
<div>{{ count }}</div>
<button @click="increaseCount">increaseCount</button>
</template>
<script setup>
import { ref, watch } from "vue";
// 定义ref类型的响应式数据
const count = ref(1);
// 监听数据变化
watch(count, (newValue, oldValue) => {
console.log(newValue, oldValue);
});
const increaseCount = () => {
// 修改数据
count.value = count.value * 2;
};
</script>
一、ref
// ref函数
function ref(value) {
return createRef(value, false);
}
// createRef函数
function createRef(rawValue, shallow) {
// 如果是ref,直接返回
if (isRef(rawValue)) {
return rawValue;
}
// 创建RefImpl实例
return new RefImpl(rawValue, shallow);
}
// 定义RefImpl类(包含get和set函数)
class RefImpl {
constructor(value, __v_isShallow) {
this.__v_isShallow = __v_isShallow;
this.dep = void 0;
this.__v_isRef = true;
this._rawValue = __v_isShallow ? value : toRaw(value);
this._value = __v_isShallow ? value : toReactive(value);
}
// 数据被访问时
get value() {
// 依赖收集,当前例子中是收集watch的effect
trackRefValue(this);
// 返回响应式数据
return this._value;
}
set value(newVal) {
const useDirectValue =
this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
newVal = useDirectValue ? newVal : toRaw(newVal);
// 判断数据是否发生了莲花
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal;
this._value = useDirectValue ? newVal : toReactive(newVal);
// 派发更新,当前例子中派发的是watch中的effect
triggerRefValue(this, 4, newVal);
}
}
}
以上是响应式 apiref
的基本逻辑,最终是实例化了RefImpl
,当访问到该数据时,会通过trackRefValue
来实现依赖收集,当修改其数据时,又会通过triggerRefValue
的方式进行派发更新。
二、watch
function watch(source, cb, options) {
// 如果cb不是函数,控制台打印警告
if (!!(process.env.NODE_ENV !== "production") && !isFunction(cb)) {
warn$1(
`\`watch(fn, options?)\` signature has been moved to a separate API. Use \`watchEffect(fn, options?)\` instead. \`watch\` now only supports \`watch(source, cb, options?) signature.`
);
}
return doWatch(source, cb, options);
}
function doWatch(
source,
cb,
{ immediate, deep, flush, once, onTrack, onTrigger } = EMPTY_OBJ
) {
let getter;
let forceTrigger = false;
let isMultiSource = false;
// 1、通过source获取getter
// source是ref
if (isRef(source)) {
getter = () => source.value;
forceTrigger = isShallow(source);
// source是reactive
} else if (isReactive(source)) {
getter = () => reactiveGetter(source);
forceTrigger = true;
// source是数组
} else if (isArray(source)) {
isMultiSource = true;
forceTrigger = source.some((s) => isReactive(s) || isShallow(s));
getter = () =>
source.map((s) => {
if (isRef(s)) {
return s.value;
} else if (isReactive(s)) {
return reactiveGetter(s);
} else if (isFunction(s)) {
return callWithErrorHandling(s, instance, 2);
} else {
!!(process.env.NODE_ENV !== "production") && warnInvalidSource(s);
}
});
// source是函数
} else if (isFunction(source)) {
if (cb) {
getter = () => callWithErrorHandling(source, instance, 2);
} else {
getter = () => {
if (cleanup) {
cleanup();
}
return callWithAsyncErrorHandling(source, instance, 3, [onCleanup]);
};
}
// 否则为空函数
} else {
getter = NOOP;
!!(process.env.NODE_ENV !== "production") && warnInvalidSource(source);
}
if (cb && deep) {
const baseGetter = getter;
getter = () => traverse(baseGetter());
}
let cleanup;
let onCleanup = (fn) => {
cleanup = effect.onStop = () => {
callWithErrorHandling(fn, instance, 4);
cleanup = effect.onStop = void 0;
};
};
let oldValue = isMultiSource
? new Array(source.length).fill(INITIAL_WATCHER_VALUE)
: INITIAL_WATCHER_VALUE;
// 2、定义scheduler
const job = () => {
if (!effect.active || !effect.dirty) {
return;
}
if (cb) {
const newValue = effect.run();
if (
deep ||
forceTrigger ||
(isMultiSource
? newValue.some((v, i) => hasChanged(v, oldValue[i]))
: hasChanged(newValue, oldValue)) ||
false
) {
if (cleanup) {
cleanup();
}
callWithAsyncErrorHandling(cb, instance, 3, [
newValue,
// pass undefined as the old value when it's changed for the first time
oldValue === INITIAL_WATCHER_VALUE
? void 0
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
? []
: oldValue,
onCleanup,
]);
oldValue = newValue;
}
} else {
effect.run();
}
};
job.allowRecurse = !!cb;
let scheduler;
if (flush === "sync") {
scheduler = job;
} else if (flush === "post") {
scheduler = () => queuePostRenderEffect(job, instance && instance.suspense);
} else {
// 用于后续事件优先级的排序(在后续流程中用到,pre表示watch在job.id相同的时候有更高的执行优先级)
job.pre = true;
if (instance) job.id = instance.uid;
scheduler = () => queueJob(job);
}
// 3、创建ReactiveEffect实例
const effect = new ReactiveEffect(getter, NOOP, scheduler);
const scope = getCurrentScope();
const unwatch = () => {
effect.stop();
if (scope) {
remove(scope.effects, effect);
}
};
// 4、执行job/effect.run()
if (cb) {
if (immediate) {
job();
} else {
// 当前例子中执行effect.run()
oldValue = effect.run();
}
}
// 5、返回unwatch
return unwatch;
}
这里主要根据条件获取getter
和scheduler
,然后通过const effect = new ReactiveEffect(getter, NOOP, scheduler)
创建侦听器effect
,然后,根据immediate
执行job()
或oldValue = effect.run()
,最后返回侦听器销毁函数unwatch
。
这里重点关注oldValue = effect.run()
,这里会执行数据实例RefImpl
的依赖收集过程。
三、依赖收集
run() {
this._dirtyLevel = 0;
if (!this.active) {
return this.fn();
}
let lastShouldTrack = shouldTrack;
let lastEffect = activeEffect;
try {
shouldTrack = true;
activeEffect = this;
this._runnings++;
preCleanupEffect(this);
return this.fn();
} finally {
postCleanupEffect(this);
this._runnings--;
activeEffect = lastEffect;
shouldTrack = lastShouldTrack;
}
}
activeEffect = this
将侦听器effect
作为当前激活状态的activeEffect
。
this.fn()
就是getter = () => source.value
,结合当前例子,这里访问到了count.value
,触发了RefImpl
的get
函数。
// get函数
function get() {
trackRefValue(this);
return this._value;
}
// 跟踪Ref的值
function trackRefValue(ref2) {
var _a;
if (shouldTrack && activeEffect) {
ref2 = toRaw(ref2);
trackEffect(
activeEffect,
(_a = ref2.dep) != null
? _a
: (ref2.dep = createDep(
() => (ref2.dep = void 0),
ref2 instanceof ComputedRefImpl ? ref2 : void 0
)),
!!(process.env.NODE_ENV !== "production")
? {
target: ref2,
type: "get",
key: "value",
}
: void 0
);
}
}
// 依赖收集函数
function trackEffect(effect2, dep, debuggerEventExtraInfo) {
var _a;
if (dep.get(effect2) !== effect2._trackId) {
dep.set(effect2, effect2._trackId);
const oldDep = effect2.deps[effect2._depsLength];
if (oldDep !== dep) {
if (oldDep) {
cleanupDepEffect(oldDep, effect2);
}
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));
}
}
}
trackRefValue
中将activeEffect
和ref2.dep
作为参数传入。
trackEffect
中将当前激活的effect2
作为key
,effect2._trackId
作为value
收集到dep
中。同时,通过effect2.deps[effect2._depsLength++] = dep
的方式将dep
记录到deps
中去。
至此,响应式数据ref
和侦听器watch
之间的关系完成建立。
在run
逻辑执行结束后,通过activeEffect = lastEffect
将当前激活的effect
赋值为undefined
。
附:在执行render
的过程中(已知activeEffect
是渲染effect
),会访问到count.value
,继而收集渲染effect
。
最终,RefImpl
收集了侦听器effect
和渲染effect
。
四、派发更新
当执行数据变化的函数const increaseCount = () => {count.value = count.value * 2;}
时,会触发RefImpl
中的set
函数,进而执行到派发更新的流程triggerRefValue(this, 4, newVal)
:
function triggerRefValue(ref2, dirtyLevel = 4, newVal) {
ref2 = toRaw(ref2);
// 获取收集到的依赖
const dep = ref2.dep;
if (dep) {
triggerEffects(
dep,
dirtyLevel,
!!(process.env.NODE_ENV !== "production")
? {
target: ref2,
type: "set",
key: "value",
newValue: newVal,
}
: void 0
);
}
}
function triggerEffects(dep, dirtyLevel, debuggerEventExtraInfo) {
var _a;
pauseScheduling();
for (const effect2 of dep.keys()) {
let tracking;
if (
effect2._dirtyLevel < dirtyLevel &&
(tracking != null
? tracking
: (tracking = dep.get(effect2) === effect2._trackId))
) {
// 判断watch-effect的属性effect2._shouldSchedule是否为真,这里明显为true
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
) {
effect2._shouldSchedule = false;
if (effect2.scheduler) {
// 当前例子中会将watch-effect对应的scheduler推入到queueEffectSchedulers
queueEffectSchedulers.push(effect2.scheduler);
}
}
}
}
resetScheduling();
}
// pauseScheduling
function pauseScheduling() {
pauseScheduleStack++;
}
// resetScheduling
function resetScheduling() {
pauseScheduleStack--;
while (!pauseScheduleStack && queueEffectSchedulers.length) {
queueEffectSchedulers.shift()();
}
}
当执行到resetScheduling
时,queueEffectSchedulers.shift()()
相当于执行了scheduler = () => queueJob(job)
:
function queueJob(job) {
if (
!queue.length ||
!queue.includes(
job,
isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
)
) {
if (job.id == null) {
queue.push(job);
} else {
// 根据其id放到一个合适的位置
queue.splice(findInsertionIndex(job.id), 0, job);
}
queueFlush();
}
}
function queueFlush() {
if (!isFlushing && !isFlushPending) {
isFlushPending = true;
// flushJobs会在下一个异步队列中执行
currentFlushPromise = resolvedPromise.then(flushJobs);
}
}
function flushJobs(seen) {
isFlushPending = false;
isFlushing = true;
queue.sort(comparator);
const check = !!(process.env.NODE_ENV !== "production")
? (job) => checkRecursiveUpdates(seen, job)
: NOOP;
try {
for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
// 获取job
const job = queue[flushIndex];
if (job && job.active !== false) {
// 执行job,具体job的流程请自行断点调试。
callWithErrorHandling(job, null, 14);
}
}
} finally {
flushIndex = 0;
queue.length = 0;
flushPostFlushCbs(seen);
isFlushing = false;
currentFlushPromise = null;
if (queue.length || pendingPostFlushCbs.length) {
flushJobs(seen);
}
}
}
这里注意事件队列queue
的排序规则comparator
:
const comparator = (a, b) => {
const diff = getId(a) - getId(b);
if (diff === 0) {
if (a.pre && !b.pre) return -1;
if (b.pre && !a.pre) return 1;
}
return diff;
};
这里的pre
就印证了watch
之前设置的job.pre = true
,也说明在job.id
相同时,watch
事件具有更高的优先级。
至此,我们就简单介绍了
watch
底层的大体流程,首先,watch
它只能被收集,是作为订阅者
的形式存在的,既然是订阅者
,那么它就需要订阅发布者
的消息。当前例子中是用了ref
作为发布者,当然也可以用reactive
等具有��应式属性的数据。
转载自:https://juejin.cn/post/7393306884538269705