第十三节:vue3 组件的渲染和更新原理

376 阅读2分钟

组件的挂载流程

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

data 类似于reactive

const VueComponent = {
    data(){ // 类似于reactive
        return {name: 'lyp', age:18} 
    },
    render(){ // 类似effect
        setTimeout(()=>{
            this.age++
        },1000)
        return h('p',`${this.name}今年${this.age}岁了`)
    }
}
render(h(VueComponent), app)

添加组件类型

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

// packages/runtime-core/src/vnode.ts
export const createVNode = (type,props,children = null)=>{
    const shapeFlag = isString(type)  
        ? ShapeFlags.ELEMENT: isObject(type)
        ? ShapeFlags.STATEFUL_COMPONENT:0;
    // ... 稍后可以根据类型来进行组件的挂载
}

组件的渲染 processComponent

//packages/runtime-core/src/renderer.ts
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)
            }
    }
}
const processComponent = (n1,n2,container,anchor)=>{ // 统一处理组件渲染 里边处理是函数式组件还是普通组件
    if(n1 == null){
        mountComponent(n2,container,anchor);
    }else{
        // 组件更新逻辑
    }
}
const mountComponent = (n2,container,anchor)=>{
    const {render,data=()=>({})} = n2.type; // 1、取出 data render
    const state = reactive(data()) // 2、组件的状态
    const instance = { // 3、组件的实例
        state, // 组件的状态
        isMounted:false, // 组件是否挂载
        subTree:null, // 子树(子节点) 渲染组件的内容
        update:null, // 
        vnode: n2   // 组件的虚拟节点
    }
    const componentUpdateFn = ()=>{ //4、组件更新函数 区分组件要更新还是初始化
        if(!instance.isMounted){ // 初始化
            const subTree = render.call(state,state); //作为this选项 后续this会更改
            patch(null,subTree,container,anchor);  // 创造了subTree的真实节点并且插入
            instance.subTree = subTree
            
            instance.isMounted = true;
        }else{ // 组件内部更新
            const subTree = render.call(state,state); // 拿到新的
            patch(instance.subTree,subTree,container,anchor) // 和老的比对
            instance.subTree = subTree // 更新
        }
    }
    // 5、组件的渲染
    const effect = new ReactiveEffect(componentUpdateFn)
    // 6、调用 effect.run() 可以让组件强制更新
    const update = instance.update = effect.run.bind(effect);
    update(); 
}

组件异步渲染

组件的更新不能是同步的, 会出现叠加更新的情况

修改上边的mountComponent方法的调度函数,将更新方法压入到队列中

// 组件的异步渲染 批处理操作scheduler
const effect = new ReactiveEffect(componentUpdateFn,()=>queueJob(instance.update) )
const update = instance.update = effect.run.bind(effect);

批处理操作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)
            for(let i = 0; i < copy.length;i++){
                let job = copy[i];
                job();
            }
            queue.length = 0;
            copy.length = 0;
        })
    }
}