Skip to content

组件的挂载流程

组件需要提供一个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;
            let copy = queue.slice(0)
            queue.length = 0; // 这里要先清空,防止在执行过程中在加入新的job
            for(let i = 0; i < queue.length;i++){
                let job = queue[i];
                job();
            }
            copy.length = 0;
        })
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

组件Props、Attrs实现

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

let {createRenderer,h,render,Text,Fragment} = VueRuntimeDOM
const VueComponent  = { 
    data(){
        return {name:'zf',age:13}
    },
    props:{
        address:String,
    },
    render(){ 
        return h('p',[`${this.name}今年${this.age}岁了`,`${this.address}`,`${this.$attrs.a}${this.$attrs.b}`]);
    }
}
render(h(VueComponent,{address:'霍营',a:1,b:2}),app);
1
2
3
4
5
6
7
8
9
10
11
12
13

initProps

const mountComponent = (vnode,container,anchor) =>{
    let {data=()=>({}),render,props:propsOptions = {}} = vnode.type; // 这个就是用户写的内容
    const state = reactive(data()); // pinia 源码就是 reactive({})  作为组件的状态
    const instance = { // 组件的实例
        state,
        vnode,  // vue2的源码中组件的虚拟节点叫$vnode  渲染的内容叫_vnode
        subTree:null, // vnode组件的虚拟节点   subTree渲染的组件内容
        isMounted:false,
        update:null,
        propsOptions,
        attrs:{},
        props:{}
    }
    vnode.component = instance
    initProps(instance,vnode.props);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

componentProps.ts

export function initProps(instance,rawProps){
    const props = {};
    const attrs = {};
    const options = instance.propsOptions || {}; // 获取组件用户的配置
    if(rawProps){
        for(let key in rawProps){
            const value = rawProps[key];
            if( key in options){
                props[key] = value;
            }else{
                attrs[key] = value
            }
        }
    }
    instance.props = reactive(props); // 这里应该用shallowReactive,遵循单向数据流原则
    instance.attrs = attrs;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

属性代理

const publicPropertiesMap = {
    $attrs:i=> i.attrs
}
const mountComponent = (vnode,container,anchor) =>{
	// ...
    const instance = { // 组件的实例
        // ...
        proxy:null
    }
    vnode.component = instance
    initProps(instance,vnode.props);
    instance.proxy =  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
29
30
31
32
33
34
35
36
37

组件流程整合

const mountComponent = (vnode,container,anchor) =>{
    // 1) 创建实例
    const instance = vnode.component = createComponentInstance(vnode);
    // 2) 给实例赋值
    setupComponent(instance)
    // 3) 创建渲染effect及更新
    setupRenderEffect(instance,container,anchor);
}
1
2
3
4
5
6
7
8

1)创建组件实例

component.ts

export function createComponentInstance(vnode){
    const instance = { // 组件的实例
        data:null,
        vnode,  // vue2的源码中组件的虚拟节点叫$vnode  渲染的内容叫_vnode
        subTree:null, // vnode组件的虚拟节点   subTree渲染的组件内容
        isMounted:false,
        update:null,
        attrs:{},
        props:{},
        proxy:null,
        propsOptions:vnode.type.props
    }
    return instance
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

2)设置组件属性

const publicPropertiesMap = {
    $attrs:i=> i.attrs
}
const PublicInstanceProxyHandlers = {
    get(target,key){
        const {data,props} = target;
        if(data && hasOwn(data,key)){
            return data[key];
        }else if(hasOwn(props,key)){
            return props[key];
        }
        const publicGetter = publicPropertiesMap[key];
        if(publicGetter){
            return publicGetter(target)
        }
    },
    set(target,key,value){
        const {data,props} = target;
        if(data && hasOwn(data,key)){
            data[key] = value;
            return true;
        }else if(hasOwn(props,key)){
            console.warn(`Attempting to mutate prop "${key}". Props are readonly.`)
            return false;
        } 
        return true;
    }
}
export function setupComponent(instance){
    const {props,type} = instance.vnode;
    initProps(instance,props);
    instance.proxy = new Proxy(instance,PublicInstanceProxyHandlers);
    const data = type.data;
    if(data){
        if(!isFunction(data)) return console.warn('The data option must be a function.')
        instance.data = reactive(data.call(instance.proxy))
    }
    instance.render = type.render
}
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

3)渲染effect

 const setupRenderEffect = (instance,container,anchor) =>{
     const {render} = instance
     const componentUpdateFn = () =>{ // 区分是初始化 还是要更新
         if(!instance.isMounted){ // 初始化
             const subTree = render.call(instance.proxy,instance.proxy); // 作为this,后续this会改
             patch(null,subTree,container,anchor); // 创造了subTree的真实节点并且插入了
             instance.subTree = subTree;
             instance.isMounted = true
         }else{ // 组件内部更新
             const subTree = render.call(instance.proxy,instance.proxy);
             patch(instance.subTree,subTree,container,anchor);
             instance.subTree = subTree;
         }
     }
     // 组件的异步更新
     const effect = new ReactiveEffect(componentUpdateFn,()=> queueJob(instance.update))
     // 我们将组件强制更新的逻辑保存到了组件的实例上,后续可以使用
     let update = instance.update = effect.run.bind(effect); // 调用effect.run可以让组件强制重新渲染
     update();
 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

属性更新

const My = {
    props:{address:String},
    render(){return h('div',this.address)}
}
const VueComponent  = { 
    data(){
        return {name:'zf',age:13,flag:false}
    },
    render(){ 
        return h(Fragment,[
            h('button',{onClick:() => this.flag = !this.flag},'切换渲染'),
            h(My,{address:this.flag? '天龙苑':'回龙观'}),
        ]) 
    }
}
render(h(VueComponent),app);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const updateComponent = (n1,n2)=>{
    const instance = (n2.component = n1.component);
    const {props:prevProps} = n1;
    const {props:nextProps} = n2;
    updateProps(instance,prevProps,nextProps)
}
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

props.ts

const hasPropsChanged = (prevProps={},nextProps={}) =>{
    const nextKeys = Object.keys(nextProps);
    if(nextKeys.length !== Object.keys(prevProps).length){
        return true;
    }
    for(let i = 0; i < nextKeys.length ; i++){
        const key = nextKeys[i];
        if(nextProps[key] !== prevProps[key]){
            return true;
        }
    }
    return false
}
export function updateProps(instance,prevProps,nextProps){
    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]
            }
        }
    }
}
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

这里我们将更新逻辑放到componentFn中,因为除了属性更新之外,插槽也会导致页面更新

const shouldUpdateComponent = (n1,n2) =>{
    const { props: prevProps, children: prevChildren } = n1
    const { props: nextProps, children: nextChildren } = n2;
    	
    if(prevChildren || nextChildren) return true
    
    if(prevProps === nextProps) return false;
    return hasPropsChanged(prevProps,nextProps)
}
const updateComponent = (n1,n2)=>{
    const instance = (n2.component = n1.component);
    if(shouldUpdateComponent(n1,n2)){
        instance.next = n2 // 将新的虚拟节点放到next属性上
        instance.update(); // 属性变化手动调用更新方法
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export function updateProps(prevProps,nextProps){
    for(const key in nextProps){ // 循环props
        prevProps[key] = nextProps[key]; // 响应式属性更新后会重新渲染
    }
    for(const key in prevProps){ // 循环props
        if(!(key in nextProps)){
            delete prevProps[key]
        }
    }
}
function updateComponentPreRender(instance,next){
    instance.next = null;
    instance.vnode = next;
    updateProps(instance.props,next.props)
}
const componentUpdateFn = ()=>{
    if(!instance.isMounted){
        // ...
    }else{
        let {next} = instance;
        if(next){
            updateComponentPreRender(instance,next)
        }
        const subTree = render.call(instance.proxy,instance.proxy);
        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
26
27
28

Released under the MIT License.