组件更新过程 (上)

143 阅读3分钟

1、核心入口代码:

const setupRenderEffect: SetupRenderEffectFn = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {    
    // 创建响应式的副作用渲染函数        
    instance.update = effect(function componentEffect() {        
        if (!instance.isMounted) {            
        //渲染组件              
        } else {            
            // 更新组件                    
            let { next, bu, u, parent, vnode } = instance            // next 表示新的组件 vnode                    
            if (next) {                
                updateComponentPreRender(instance, next, optimized)            
                
            } else {  
                next = vnode            
                
            }            
            // 渲染新的子树 vnode                    
            const nextTree = renderComponentRoot(instance)            // 缓存旧的子树 vnode                    
            const prevTree = instance.subTree            
            // 更新子树 vnode            
            instance.subTree = nextTree            
            // 组件更新核心逻辑,根据新旧子树 vnode 做 patch                   
            patch(prevTree,
                nextTree,                
                // 如果在 teleport 组件中父节点可能已经改变,所以容器直接找旧树 DOM 元素的父节点                          
                hostParentNode(prevTree.el!)!,                
                // 参考节点在 fragment 的情况可能改变,所以直接找旧树 DOM 元素的下一个节点                          
                getNextHostNode(prevTree), 
                instance,
                parentSuspense,
                isSVG
            )            
            // 缓存更新后的 DOM 节点                   
            next.el = nextTree.el        
            
        }    
        
    }, prodEffectOptions)}

2、核心逻辑:patch 流程

const patch: PatchFn = (n1,n2,container,anchor = null,parentComponent = null,parentSuspense = null, isSVG = false, slotScopeIds = null,    optimized = false  ) => { 
// 如果存在新旧节点, 且新旧节点类型不同,则销毁旧节点    
if (n1 && !isSameVNodeType(n1, n2)) {      
    anchor = getNextHostNode(n1)      
    unmount(n1, parentComponent, parentSuspense, true)      
    n1 = null
}    
const { type, ref, shapeFlag } = n2 
switch (type) {      
    case Text: // 处理文本节点
        break  
    case Comment:// 处理注释节点
        break
    case Static:// 处理静态节点
        break
    case Fragment:// 处理 Fragment 元素
        break
    default:
        if (shapeFlag & ShapeFlags.ELEMENT) {// 处理普通 DOM 元素
            processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG,            slotScopeIds, optimized)} 
        else if (shapeFlag & ShapeFlags.COMPONENT) {
         // 处理组件 
            processComponent( n1, n2, container, anchor,            parentComponent, parentSuspense, isSVG,            slotScopeIds,  optimized)
        } else if (shapeFlag & ShapeFlags.TELEPORT) { // 处理 TELEPORT
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {          // 处理 SUSPENSE
        }
    }  
}

3、组件更新:

const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement, 
    anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, 
    parentSuspense: SuspenseBoundary | null, isSVG: boolean, slotScopeIds: string[] | null,
optimized: boolean  ) => { 
    n2.slotScopeIds = slotScopeIds 
    if (n1 == null) { 
        // 挂载组件    
    } else {
      //更新子组件      
      updateComponent(n1, n2, optimized)    
    }  
}

const updateComponent = (n1: VNode, n2: VNode, optimized: boolean) => {    
        const instance = (n2.component = n1.component)!
        // 根据新旧子组件 vnode 判断是否需要更新子组件    
        if (shouldUpdateComponent(n1, n2, optimized)) {        
            // 新的子组件 vnode 赋值给 instance.next        
            instance.next = n2        
            // 子组件也可能因为数据变化被添加到更新队列里了,移除它们防止对一个子组件重复更新        
            invalidateJob(instance.update) 
            // 执行子组件的副作用渲染函数        
            instance.update()      
        
        }    
    
    } else {      
        // 不需要更新,只复制属性      
        n2.component = n1.component      
        n2.el = n1.el      
        instance.vnode = n2    
        
    }  
}

可以看到,processComponent 主要通过执行 updateComponent 函数来更新子组件,updateComponent 函数在更新子组件的时候,会先执行 shouldUpdateComponent 函数,根据新旧子组件 vnode 来判断是否需要更新子组件。这里你只需要知道,在 shouldUpdateComponent 函数的内部,主要是通过检测和对比组件 vnode 中的 props、chidren、dirs、transiton 等属性,来决定子组件是否需要更新.

4、普通元素更新:

const processElement = (    
    n1: VNode | null,    
    n2: VNode,    
    container: RendererElement,    
    anchor: RendererNode | null,   
    parentComponent: ComponentInternalInstance | null,    
    parentSuspense: SuspenseBoundary | null,    
    isSVG: boolean,    
    slotScopeIds: string[] | null,    
    optimized: boolean  
    ) => {    
            if (n1 == null) {      
                // 挂载元素    } 
            
            else {      
                // 更新元素
            
                patchElement(       
                    n1,        
                    n2,        
                    parentComponent,        
                    parentSuspense,        
                    isSVG,        
                    slotScopeIds,        
                    optimized      
                )    
            
             }  
            
        }

const patchElement = (    
    n1: VNode,    
    n2: VNode,    
    parentComponent: ComponentInternalInstance | null,   
    parentSuspense: SuspenseBoundary | null,    
    isSVG: boolean,    
    slotScopeIds: string[] | null,    
    optimized: boolean  ) => {    
        const el = (n2.el = n1.el!)    
        const oldProps = n1.props || EMPTY_OBJ    
        const newProps = n2.props || EMPTY_OBJ    
        patchProps(        
            el,        
            n2,        
            oldProps,        
            newProps,        
            parentComponent,        
            parentSuspense,        
            isSVG      
            )
     const areChildrenSVG = isSVG && n2.type !== 'foreignObject'
     patchChildren(        
         n1,        
         n2,        
         el,        
         null,       
         parentComponent,        
         parentSuspense,        
         areChildrenSVG,        
         slotScopeIds,        
         false      
         )    
        
    }

可以看到,更新元素的过程主要做两件事情:更新 props 和更新子节点。其实这是很好理解的,因为一个 DOM 节点元素就是由它自身的一些属性和子节点构成的。