setup函数作用

组件的render函数每次更新时都会重新执行,但是setup函数只会在组件挂载时执行一次。

  • setup函数是compositionAPI的入口
  • 可以在函数内部编写逻辑,解决vue2中反复横跳问题
  • setup返回函数时为组件的render函数,返回对象时对象中的数据将暴露给模板使用
  • setup中函数的参数为props、context({slots,emit,attrs,expose})
const mountComponent = (n2,container,anchor)=>{
    let {render,data=()=>({}),props:propsOptions={},setup} = n2.type;
    const state = reactive(data())
    const instance = {
        state, // 组件的状态
        isMounted:false, // 组件是否挂载
        subTree:null, // 子树
        update:null,
        attrs:{},
        props:{},
        next:null,
        setupState:null,
        vnode:n2
    }
    n2.component = instance;
    // 用户写的props 及 传入的props
    initProps(instance,propsOptions,n2.props); // 初始化属性
    
    if(setup){ // 对setup做相应处理
        const setupContext = {};
        const setupResult = setup(instance.props,setupContext);
        if(isFunction(setupResult)){
            render = setupResult;
        }else if(isObject(setupResult)){
            instance.setupState = proxyRefs(setupResult)
        }
    }
    const renderContext = new Proxy(instance,{
        get(target,key){
            const {state,props,setupState} = target;
            if(state && hasOwn(state,key)){
                return state[key];
            }else if(hasOwn(props,key)){
                return props[key];
            }else if(setupState && hasOwn(setupState,key)){ // setup返回值做代理
                return setupState[key];
            }
            const publicGetter = publicPropertiesMap[key];
            if(publicGetter){
                return publicGetter(instance)
            }
        },
        set(target,key,value){
            const {state,props} = target;
            if(state && hasOwn(state,key)){
                state[key] = value;
                return true;
            }else if(hasOwn(props,key)){
                console.warn(`Attempting to mutate prop "${key}". Props are readonly.`)
                return false;
            } 
            return true;
        }
    });
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

实现emit方法

const VueComponent = {
    setup(props,ctx){
        const handleClick = ()=>{
            ctx.emit('myEvent');
        }
        return ()=>h('button',{onClick:handleClick},'点我啊')
    }
}
const app = createApp(h(VueComponent,{onMyEvent:()=>{alert(1000)}}))
app.mount(document.getElementById('app'))
1
2
3
4
5
6
7
8
9
10
const setupContext = {
    attrs:instance.attrs,
    emit:(event,...args)=>{
        const eventName = `on${event[0].toUpperCase() + event.slice(1)}`;
        const handler = instance.vnode.props[eventName]; // 找到绑定的方法
        // 触发方法执行
        handler && handler(...args);
    }
};
1
2
3
4
5
6
7
8
9

slot实现

const MyComponent = {
    render(){
        return h(Fragment,[
            h('div',[this.$slots.header()]), // 获取插槽渲染
            h('div',[this.$slots.body()]),
            h('div',[this.$slots.footer()]),
        ])
    }
}
const VueComponent = {
    setup(){
        return ()=>h(MyComponent,{ // 渲染组件时传递对应的插槽属性
            header:() => h('p','头'),
            body:() => h('p','体'),
            footer:() => h('p','尾')
        })
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export const createVNode = (type,props,children = null)=>{
    // ....
    if(children){
        let type = 0;
        if(Array.isArray(children)){
            type = ShapeFlags.ARRAY_CHILDREN;
        }else if(isObject(children)){ // 类型是插槽
            type = ShapeFlags.SLOTS_CHILDREN
        }else{
            children = String(children);
            type = ShapeFlags.TEXT_CHILDREN
        }
        vnode.shapeFlag |= type
    }
    return vnode;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const publicPropertiesMap = {
    $attrs:i=> i.attrs,
    $slots:i=>i.slots
}
function initSlots(instance,children){
    if(instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN){
        instance.slots = children;
    }else{
        instance.slots = {};
    }
}
const mountComponent = (n2,container,anchor)=>{
    const instance = {
        // ...
        slots:null
    }
    // ...
    initProps(instance,propsOptions,n2.props); 
    initSlots(instance,n2.children) // 初始化插槽

    if(setup){ // 对setup做相应处理
        const setupContext = {
            // ...
            slots: instance.slots // 挂载插槽
        };
    }
}
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

生命周期实现原理

export let currentInstance = null
export function setCurrentInstance(instance){
    currentInstance = instance // 用于记住当前实例
} 
1
2
3
4
setCurrentInstance(instance); // 在调用setup的时候保存当前实例
const setupResult = setup(instance.props,setupContext);
setCurrentInstance(null);
1
2
3

创建生命周期钩子

export const enum LifecycleHooks {
    BEFORE_MOUNT = 'bm',
    MOUNTED = 'm',
    BEFORE_UPDATE = 'bu',
    UPDATED = 'u'
}
function createHook(type){
    return (hook,target = currentInstance) =>{ // 调用的时候保存当前实例
        if(target){
            const hooks = target[type] || (target[type] = []);
            const wrappedHook = () =>{
                setCurrentInstance(target); // 当生命周期调用时 保证currentInstance是正确的
                hook.call(target); 
                setCurrentInstance(null);
            }
            hooks.push(wrappedHook);
        }
    }
}
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
22
23

钩子调用

const componentUpdateFn = ()=>{
    if(!instance.isMounted){
        const {bm,m} = instance
        if(bm){ // beforeMount
            invokeArrayFns(bm)
        }
        const subTree = render.call(renderContext,renderContext);
        patch(null,subTree,container,anchor);
        if(m){ // mounted
            invokeArrayFns(m)
        }
        instance.subTree = subTree
        instance.isMounted = true;
    }else{
        let {next,bu,u} = instance;
        if(next){
            updateComponentPreRender(instance,next)
        }
        if(bu){ // beforeUpdate
            invokeArrayFns(bu)
        }
        const subTree = render.call(renderContext,renderContext);
        patch(instance.subTree,subTree,container,anchor)
        if(u){ // updated
            invokeArrayFns(u)
        }
        instance.subTree = subTree
    }
}
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
export const invokeArrayFns = (fns) => {
    for (let i = 0; i < fns.length; i++) {
        fns[i](); // 调用钩子方法
    }
}
1
2
3
4
5