组件的挂载流程

组件需要提供一个render函数,渲染函数需要返回虚拟DOM

const VueComponent = {
    data(){
        return {age:13} 
    },
    render(){
        return h('p',[h(Text,"I'm Jiang sir"),h('span',this.age+'')])
    }
}
createRenderer(renderOptions).render(h(VueComponent),document.getElementById('app'))
1
2
3
4
5
6
7
8
9

添加组件类型

h方法中传入一个对象说明要渲染的是一个组件。(后续还有其他可能)

export const createVNode = (type,props,children = null)=>{
    const shapeFlag = isString(type)  
        ? ShapeFlags.ELEMENT: isObject(type)
        ? ShapeFlags.STATEFUL_COMPONENT:0;
    // ... 稍后可以根据类型来进行组件的挂载
}
1
2
3
4
5
6

组件的渲染

const patch = (n1,n2,container,anchor?) => {
    // 初始化和diff算法都在这里喲
    if(n1 == n2){return }
    if(n1 && !isSameVNodeType(n1,n2)){ // 有n1 是n1和n2不是同一个节点
        unmount(n1)
        n1 = null
    }
    const {type,shapeFlag} = n2;
    switch(type){
        // ...
        default:
            if(shapeFlag & ShapeFlags.ELEMENT){
                processElement(n1,n2,container,anchor)
            }else if(shapeFlag & ShapeFlags.COMPONENT){
                processComponent(n1,n2,container,anchor)
            }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const mountComponent = (n2,container,anchor)=>{
    const {render,data=()=>({})} = n2.type;
    const state = reactive(data())
    const instance = {
        state, // 组件的状态
        isMounted:false, // 组件是否挂载
        subTree:null, // 子树
        update:null,
        vnode:n2
    }
    const componentUpdateFn = ()=>{
        if(!instance.isMounted){
            const subTree = render.call(state,state);
            patch(null,subTree,container,anchor);
            instance.subTree = subTree
            instance.isMounted = true;
        }else{
            const subTree = render.call(state,state);
            patch(instance.subTree,subTree,container,anchor)
            instance.subTree = subTree
        }
    }
    const effect = new ReactiveEffect(componentUpdateFn)
    const update = instance.update = effect.run.bind(effect);
    update();
}
const processComponent = (n1,n2,container,anchor)=>{
    if(n1 == null){
        mountComponent(n2,container,anchor);
    }else{
        // 组件更新逻辑
    }
}
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

组件异步渲染

修改调度方法,将更新方法压入到队列中

const effect = new ReactiveEffect(
    componentUpdateFn,
    ()=>queueJob(instance.update) 
);
const update = instance.update = effect.run.bind(effect);
1
2
3
4
5

批处理操作scheduler.js

const queue = [];
let isFlushing = false;
const resolvedPromise = Promise.resolve()
export function queueJob(job){
    if(!queue.includes(job)){
        queue.push(job);
    }
    if(!isFlushing){
        isFlushing = true;
        resolvedPromise.then(()=>{
            isFlushing = false;
            for(let i = 0; i < queue.length;i++){
                let job = queue[i];
                job();
            }
            queue.length = 0;
        })
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

组件Props、Attrs实现

PropsAttrs关系是:没有定义在component.props中的属性将存储到attrs对象中

const VueComponent = {
    data(){
        return {age:13} 
    },
    props:{
        address:String
    },
    render(){
        return h('p',[
            h(Text,"I'm Jiang sir"),
            h('span',this.age),
            h('span',this.address),
            h(Text,this.$attrs.a + this.$attrs.b)
        ])
    }
}
createRenderer(renderOptions).render(h(VueComponent,{address:'天龙苑',a:1,b:2}),document.getElementById('app'))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

initProps

function initProps(instance,propsOptions,propsData){
    const props = {};
    const attrs = {};
    for(const key in propsData){
        if(key in propsOptions){ // 如果组件中声明了
            props[key] = propsData[key];
        }else{
            attrs[key] = propsData[key];
        }
    }
    instance.props = reactive(props);
    instance.attrs = attrs
}
const mountComponent = (n2,container,anchor)=>{
    const {render,data=()=>({}),props:propsOptions={}} = n2.type;
    const state = reactive(data())
    const instance = {
        state, // 组件的状态
        isMounted:false, // 组件是否挂载
        subTree:null, // 子树
        update:null,
        vnode:n2,
        attrs:{},
        props:{},
    }
    n2.component = instance; // 用于更新
    //               用户写的props 及 传入的props
    initProps(instance,propsOptions,n2.props); // 初始化属性
}
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

属性代理

const publicPropertiesMap = {
    $attrs:i=> i.attrs
}
const renderContext = new Proxy(instance,{
    get(target,key){
        const {state,props} = target;
        if(state && hasOwn(state,key)){
            return state[key];
        }else if(hasOwn(props,key)){
            return props[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

属性更新

const My = {
    props:{address:String},
    render(){return h('div',this.address)}
}
const VueComponent = {
    data(){return {flag:true}},
    props:{address:String},
    render(){
        setTimeout(()=>{
            this.flag = false;
        },1000)
        return h(My,{address:this.flag ? '天龙苑' : '回龙观'})
    }
}
createRenderer(renderOptions).render(h(VueComponent),document.getElementById('app'))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const updateComponent = (n1,n2)=>{
    const instance = (n2.component = n1.component);
    const {props:prevProps} = n1;
    const {props:nextProps} = n2;
    if(hasPropsChanged(prevProps,nextProps)){ // 比较前后属性是否一致
        for(const key in nextProps){ // 循环props
            instance.props[key] = nextProps[key]; // 响应式属性更新后会重新渲染
        }
        for(const key in instance.props){ // 循环props
            if(!(key in nextProps)){
                delete instance.props[key]
            }
        }
    }
}
const processComponent = (n1,n2,container,anchor)=>{
    if(n1 == null){
        mountComponent(n2,container,anchor);
    }else{
        // 组件更新逻辑
        updateComponent(n1,n2)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

这里我们将更新逻辑放到componentFn中

const updateComponent = (n1,n2)=>{
    const instance = (n2.component = n1.component);
    const {props:prevProps} = n1;
    const {props:nextProps} = n2;
    if(hasPropsChanged(prevProps,nextProps)){
        instance.next = n2 // 将新的虚拟节点放到next属性上
        instance.update(); // 属性变化手动调用更新方法
    }
}
1
2
3
4
5
6
7
8
9
const updateComponentPreRender = (instance,next) =>{
    instance.next = null;
    instance.vnode = next;
    for(const key in next.props){ // 循环props
        instance.props[key] = next.props[key];
    }
    for(const key in instance.props){ // 循环props
        if(!(key in next.props)){
            delete instance.props[key]
        }
    }
} 
const componentUpdateFn = ()=>{
    if(!instance.isMounted){
        // ...
    }else{
        let {next} = instance;
        if(next){ // 更新组件在渲染前更新
            updateComponentPreRender(instance,next)
        }
        const subTree = render.call(renderContext,renderContext);
        patch(instance.subTree,subTree,container,anchor)
        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