[Vue源码系列-9]vue3生命周期实现原理

782 阅读1分钟

一. 生命周期实现原理

1.1 生命周期类型

// runtime-core/component.ts
export const enum LifecycleHooks {
    BEFORE_MOUNT = 'bm',
    MOUNTED = 'm',
    BEFORE_UPDATE = 'bu',
    UPDATED = 'u',
    // ...
}

1.2 createHook & injectHook

import { currentInstance, LifecycleHooks, setCurrentInstance } from "./component"
export function injectHook(type, hook, target) {	// 通过闭包保存了当前实例
    if (target) {
        const hooks = target[type] || (target[type] = []); // 将生命周期保存在实例上
        const wrappedHook = () =>{
            setCurrentInstance(target); // 当生命周期调用时 保证currentInstance是正确的
            hook.call(target); 
            setCurrentInstance(null);
        }
        hooks.push(wrappedHook);
    }
}

export const createHook = (lifecycle) => (hook, target = currentInstance) => {	// target用来表示他是哪个实例的钩子
    injectHook(lifecycle, hook, target)	// 给当前实例 增加 对应的生命周期 即可
}

export const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);
export const onMounted = createHook(LifecycleHooks.MOUNTED);
export const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);
export const onUpdated = createHook(LifecycleHooks.UPDATED);

二. 生命周期调用

2.1 invokeArrayFns调用钩子

// shared.ts 
export const invokeArrayFns = (fns, arg?: any) => {
  for (let i = 0; i < fns.length; i++) {
    fns[i](arg)
  }
}

2.2 挂载和更新时调用钩子

instance.update = effect(function componentEffect() {
    if (!instance.isMounted) {

        const {bm, m , parent} = instance;

        if(bm){ // beforeMount
            invokeArrayFns(bm);
        }

        const proxyToUse = instance.proxy;
        const subTree = (instance.subTree = instance.render.call(proxyToUse, proxyToUse));
        patch(null, subTree, container);
        initialVNode.el = subTree.el; // 组件的el和子树的el是同一个
        instance.isMounted = true; // 组件已经挂载完毕

        if(m){ // mounted
            queuePostFlushCb(m) // 不能直接 invokeArrayFns(m);
        }
    } else {
        const prevTree = instance.subTree;
        const proxyToUse = instance.proxy;

        const {bu,u} = instance;

        if(bu){ // beforeUpdate
            invokeArrayFns(bu);
        }

        const nextTree = instance.render.call(proxyToUse, proxyToUse); 

        if(u){ // updated
            queuePostFlushCb(u);	// 不能直接 invokeArrayFns(u);
        }
        instance.subTree = nextTree

        patch(prevTree, nextTree, container);
    }
}, {
    scheduler: queueJob
})

2.3 在渲染完成后执行钩子

组件挂载和更新是异步的,直接调用invokeArrayFns的结果不准确,所以对于mountedupdated操作要特殊处理

export function queuePostFlushCb(cb) { //  cb 可能是一个数组
    queueCb(cb, pendingPostFlushCbs)
}
function queueCb(cb, pendingQueue) {
    if(!isArray(cb)){
        pendingQueue.push(cb);
    }else{
        pendingQueue.push(...cb);
    }
    queueFlush();
}

更多vue源码系列