【vue3源码笔记3】- 生命周期和调度

Vue | 2021-03-10 02:04:46 202次 0次

LifeCycle

生命周期的设计还比较有意思,使用的时候直接从 vue 中引入对应的钩子,在 setup 中使用,所以此时需要思考直接引入的这个钩子是如何和当前组件关联起来的,如下图:

微信截图_20210310154051.png

首先是组件初始化时,将当前组件实例赋值给一个全局变量,当 setup 中执行对应的钩子的时候,通过注入的方式,往当前组件实例中注入不同类型钩子的对应回调,这样的话,实现了 setup 中调用的钩子和当前组件绑定,绑定的代码 LifeCycle

下面就是组件内部执行加载、更新、卸载时,判断当前实例上有没有对应的钩子,并执行即可。这里有个需要注意的地方是,beforeMount 可以直接调用,但是 mounted 则需要放进微任务中,保证多个组件渲染后再执行各个钩子。

这里放在下面调度的部分介绍。


Scheduler

vue 中的调度,和 react 不是一个概念的,它仅仅是把任务放进一个微任务队列,这样可以提供了 effect 中的批量更新和生命周期的机制保障。

其实这里做到批量更新是依赖响应式那部分的设计机制,在第一篇文章图里看到 trigger 后面直接 run 所有的 effect 回调,那么做一个简单的修改即可,相当于开一个口子给外部传入回调:

export function trigger(
    target: object,
    key: unknown,
    newValue?: unknown
) {
    ...
    const run = (effect: ReactiveEffect) => {
        // computed 或者 对外暴露口子
        if(effect.options.scheduler){
            effect.options.scheduler(effect);
        }else{
            effect();
        }
    }
    // 执行effect
    effects.forEach(run)
}

这样的话在调用 effect 的时候,可以传入一个配置:

const setupRenderEffect: SetupRenderEffectFn = (
        instance,
        container
    ) => {
        // 等待更新时使用
        instance.update = effect(() => {
            ...
            // 同时触发数据变化,只会执行一次
        },{
            scheduler: job => {
                // 这里执行多次
                queueJob(job);
            }
        })
    }

能够做到批量更新,就要明白这个 job 参数是什么,它来自响应式的部分:

function creatReactiveEffect<T = any>(
    fn: () => T,
    options: ReactiveEffectOptions
): ReactiveEffect<T>{
    const effect = function reactiveEffect(): unknown{
        ...
        // 这里会执行fn也就是 effct调用传入的回调
    } as ReactiveEffect;
    ...
}

job 对应的就是 reactiveEffect 方法,当一个事件中触发多次数据变化是,会触发多次的 trigger,这里面每一次拿到的 reactiveEffect 都是同一个,所以就有机会在 queueJob 中做一些事情,以上的工作都是由响应式来完成的,接下来就是依赖这个结果自己处理批量执行了。

通过一个队列来存储 job,相同的 job 不再收集,并且在微任务中来清空这个队列,这样保证了同一时间多次的数据更新也只会执行 job 一次,从而拿到最后一次更新的数据刷新页面。核心逻辑如下: 

const queue: SchedulerJob[] = [];

// 任务入队
export function queueJob(job: SchedulerJob) {
    // 相同的 job 不再入队
    if (!queue.includes(job)) {
        queue.push(job)
        queueFlush()
    }
}

function queueFlush(){
    if(!isFlushPending){
        isFlushPending = true;
        currentFlushPromise = Promise.resolve().then(flushJobs)
    }
}

// 清空任务
function flushJobs(){
    isFlushPending = false;
    for (let i = 0, len = queue.length; i < len; i++) {
        // 执行 effect 中的回调
        queue[i]();
    }
    queue.length = 0;
    // 生命周期的执行
    flushPostFlushCbs();
}

vue 的调度比较清晰,第一部分提到了生命周期的调用,同理,将钩子推入一个队列,并通过微任务的方式执行,保证了这个函数一定在【组件更新后,dom 操作前执行】。


NextTick

一个语法糖,是个微任务,仅此而已

export function nextTick(
    this: any,
    fn?: () => void
): Promise<void> {
    const p = currentFlushPromise || resolvedPromise;
    return fn ? p.then(this ? fn.bind(this) : fn) : p;
}

关于 vue 的 runtime 大致思路已经描述完,接下来就是 vue 的编译部分解析。

0人赞

分享到: