我们先来分析下面这段代码的执行顺序:
import { ref, watch, nextTick, onMounted } from "vue"
const count = ref(0)
watch(count, (val) => {
console.log(val)
})
const changeCount1 = () => {
nextTick(() => {
count.value = 10 // 同步任务下的微任务
})
count.value = 20 // 同步任务下的微任务
}
const changeCount2 = () => {
setTimeout(() => {
// 宏任务
nextTick(() => {
// 当前宏任务下的微任务
count.value = 100
})
count.value = 200 //对应的watchers也是当前宏任务下的微任务
nextTick(() => {
// 当前宏任务下的微任务
count.value = 300
})
}, 0)
}
onMounted(() => {
changeCount1()
changeCount2()
})
上面代码最终的输出顺序是: 20、10、100、300
-
在这个页面之外路由currentRoute,路由currentRoute变更,新增一个微任务1到微任务队列中。
微任务1执行的函数,会遍历watcher数组,执行所有的watcher。 -
执行nextTick(count.value=10),新增微任务2到微任务队列
-
执行count.value=20,新增一个watcher到watcher数组
-
执行完同步任务后,遍历微任务队列
-
执行微任务1,遍历watcher数组,执行所有watcher,watcher数组变为空,此时输出count为20
-
执行微任务2,输出count=10
-
执行完所有微任务后,开始执行宏任务队列中的第一个宏任务,即此处的setTimeout的回调函数
-
执行nextTick(count.value=100),新增微任务3到微任务队列中
-
执行count.value=200,修改count=200,新增微任务4到微任务队列,微任务4同微任务1的回调函数,遍历watcher数组,执行watcher
-
执行nextTick(count.value = 300),新增微任务5到微任务队列中
-
宏任务中的同步脚本执行完,后遍历微任务队列
-
执行微任务3,修改count为100,修改count为100,新增一个watcher到watcher数组中
-
执行微任务4,遍历watcher数组,执行wach回调函数,此时输出count为100
-
执行微任务5,修改count=300,此时输出count为300
Vue中有三种副作用,都是通过new ReactiveEffect()创建的
- watcher 副作用, 一个组件中有多个watcher
- computed 副作用,一个组件中有多个computed
- render 副作用,一个组件中只有一个render
这些副作用都是通过new ReactiveEffect实现的
class ReactiveEffect {
constructor(fn, trigger, scheduler, scope) {
this.fn = fn;
this.trigger = trigger;
this.scheduler = scheduler;
this.active = true;
this.deps = [];
/**
* @internal
*/
this._dirtyLevel = 4;
/**
* @internal
*/
this._trackId = 0;
/**
* @internal
*/
this._runnings = 0;
/**
* @internal
*/
this._shouldSchedule = false;
/**
* @internal
*/
this._depsLength = 0;
recordEffectScope(this, scope);
}
}
创建render副作用,源码如下:
const effect = instance.effect = new ReactiveEffect(
componentUpdateFn,
_vue_shared__WEBPACK_IMPORTED_MODULE_1__.NOOP,
() => queueJob(update),
instance.scope
);
状态count的值修改后
count对应一个Dep,Dep是一个Map,Dep存放的是count对应的1个或多个ReactiveEffect(副作用)。
Vue中有一个异步队列,状态修改后,先将状态对应的副作用添加到异步队列中,再异步执行
- 执行triggerRefValue
- 执行triggerEffects,遍历Dep,将每个effect.scheduler加入到queueEffectSchedulers数组中
- 执行resetScheduling
- 遍历queueEffectSchedulers, 执行queueEffectSchedulers.shift()
- 执行queueJob,queue中push传入的job
执行queueFlush, 创建微任务,异步执行flushJobs同步任务执行完,执行flushJobs, 遍历queue执行job- 执行job即是执行watch传入的函数,computed传入的函数,或是render函数。