目录
- 【真香系列】Vue-Next 源码第一章:阅读源码的准备工作
- 【真香系列】Vue-Next 源码第二章:初始化流程上
- 【真香系列】Vue-Next 源码第三章:初始化流程下
- 【真香系列】Vue-Next 源码第四章:更新
- 【真香系列】Vue-Next 源码第五章:reative & effect
- 【真香系列】Vue-Next 源码第六章:新特性原理
- 【真香系列】Vue-Next 源码第七章:实战组件
Demo
在第一篇文章中使用了下面这个 demo
,其中用 setTimeout
模拟了一个更新,我们知道 Vue3
中使用 Proxy
代替了 Object.defineProperty
做响应拦截,在 Proxy setter
中会有一个 trigger
的触发,这个就是更新的入口。
const { reactive } = Vue
const App = {
template: `<h1>{{state.message}}</h1>`,
setup() {
const state = reactive({ message: 'World' })
setTimeout(() => {
state.message = 1
}, 3000)
return {
state
}
}
}
const app = Vue.createApp(App)
app.mount('#app')
trigger
// packages/reactivity/src/effect.ts
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
const effects = new Set<ReactiveEffect>()
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.options.allowRecurse) {
effects.add(effect)
}
})
}
}
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
depsMap.forEach(add)
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
add(depsMap.get(key))
}
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
add(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY))
}
break
}
}
const run = (effect: ReactiveEffect) => {
if (__DEV__ && effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
})
}
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
effects.forEach(run)
}
我们先看下 targetMap
的结构
首先会先在 targetMap
里查找更新目标的依赖,然后声明 effects set
,调用 add
方法去把所有更新目标相关的 value
取出来并放入 effects
,value
就是前面文章里提到过的 reactiveEffect
函数,最后遍历 effects
去执行 scheduler(queueJob)
进入任务调度流程。
const run = (effect: ReactiveEffect) => {
effect.options.scheduler(effect)
}
effects.forEach(run)
queueJob
queueJob
把 reactiveEffect
放入更新任务队列中。
export function queueJob(job: SchedulerJob) {
// the dedupe search uses the startIndex argument of Array.includes()
// by default the search index includes the current job that is being run
// so it cannot recursively trigger itself again.
// if the job is a watch() callback, the search will start with a +1 index to
// allow it recursively trigger itself - it is the user's responsibility to
// ensure it doesn't end up in an infinite loop.
if (
(!queue.length ||
!queue.includes(
job,
isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
)) &&
job !== currentPreFlushParentJob
) {
queue.push(job)
queueFlush()
}
}
queueFlush
创建微任务,flushJobs
执行时会清除微任务队列。
// packages/runtime-core/src/scheduler.ts
function queueFlush() {
if (!isFlushing && !isFlushPending) {
isFlushPending = true
currentFlushPromise = resolvedPromise.then(flushJobs)
}
}
flushJobs
执行当前更新任务。
// packages/runtime-core/src/scheduler.ts
function flushJobs(seen?: CountMap) {
isFlushPending = false
isFlushing = true
if (__DEV__) {
seen = seen || new Map()
}
flushPreFlushCbs(seen)
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child so its render effect will have smaller
// priority number)
// 2. If a component is unmounted during a parent component's update,
// its update can be skipped.
// Jobs can never be null before flush starts, since they are only invalidated
// during execution of another flushed job.
queue.sort((a, b) => getId(a!) - getId(b!))
try {
for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
const job = queue[flushIndex]
if (job) {
if (__DEV__) {
checkRecursiveUpdates(seen!, job)
}
callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
}
}
} finally {
flushIndex = 0
queue.length = 0
flushPostFlushCbs(seen)
isFlushing = false
currentFlushPromise = null
// some postFlushCb queued jobs!
// keep flushing until it drains.
if (queue.length || pendingPostFlushCbs.length) {
flushJobs(seen)
}
}
}
callWithErrorHandling
执行 reactiveEffect
,也就回到了第二篇文章里提到的 componentEffect
,也就是更新时会执行的函数,完成页面渲染。
// packages/runtime-core/src/errorHandling.ts
export function callWithErrorHandling(
fn: Function,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
args?: unknown[]
) {
let res
try {
res = args ? fn(...args) : fn()
} catch (err) {
handleError(err, instance, type)
}
return res
}
总结
更新流程里最重要的部分是任务调度系统,在第五篇文章中会详细说明。至此初始化和更新流程先介绍到这里,后续我会反复更新第 2 - 4 篇文章,争取把更细节的地方补充进去。