从零手写Vue3 生命周期实现原理

一.生命周期实现原理

定义生命周期类型 component.ts

export const enum LifecycleHooks {
    BEFORE_MOUNT = 'bm',
    MOUNTED = 'm',
    BEFORE_UPDATE = 'bu',
    UPDATED = 'u',
}
1
2
3
4
5
6

将对应的生命周期保存在实例上 apiLifecycle.ts

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) => {
    injectHook(lifecycle, hook, target)
}
// N个生命周期
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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

二.生命周期调用

shared.ts 获取每个fn执行

export const invokeArrayFns = (fns, arg?: any) => {
  for (let i = 0; i < fns.length; i++) {
    fns[i](arg)
  }
}
1
2
3
4
5
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)
        }
    } 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);
        }
        instance.subTree = nextTree

        patch(prevTree, nextTree, container);
    }
}, {
    scheduler: queueJob
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

在完成渲染后执行生命周期钩子

export function queuePostFlushCb(cb) { //  cb 可能是一个数组
    queueCb(cb, pendingPostFlushCbs)
}
function queueCb(cb, pendingQueue) {
    if(!isArray(cb)){
        pendingQueue.push(cb);
    }else{
        pendingQueue.push(...cb);
    }
    queueFlush();
}
1
2
3
4
5
6
7
8
9
10
11