vue3 - mini-vue(十一)初始化component主流程

326 阅读3分钟

initComponent

mini-vue3源码

先将整体流程写出来,具体实现细节慢慢完善

  • 创建 createApp,返回app实例

  • 引入并调用 createApp,传入App组件获取到app实例,再调用它的mount方法。

  • 创建 App 组件

    • 直接使用render函数返回虚拟节点来渲染

    • 创建 h 函数,h函数用来将DOM的描述信息转为虚拟DOM

    • setup来返回当前组件的数据

  • createVNode - 将组件信息组合为一个对象(虚拟DOM)并返回

  • render - 调用patch方法,方便进行后续递归处理

  • patch - 去处理组件

  • processComponent - 去挂载组件

  • mountComponent

    • 需要先通过虚拟节点创建一个组件实例对象,因为组件包含自己的一些属性,比如props、slots

    • setupComponent - 处理setup中的信息、初始化props、初始化slots

      • handleSetupResult - 将setup中的数据添加到实例上

      • finishComponentSetup - 将组件对象上的render添加到实例对象上

    • setupRenderEffect - 第一次渲染App组件的时候会执行,并且将render函数的this绑定为创建的代理对象

文件结构

src -> runtime-core

  • component.ts

    export function createComponentInstance (vNode) {
      const component = {
        vNode,
        type: vNode.type,
        setupState: {}
      }
      return component
    }
    
    export function setupComponent (instance) {
      // TODO
      // initProps
      // initSlots
    
      // 处理setup,将其添加到组件实例上
      setupStatefulComponent(instance)
    }
    
    function setupStatefulComponent(instance) {
      // 第一次patch的时候instance.type就是传入的App组件
      const Component = instance.type
    
      const { setup } = Component
    
      if (setup) {
        const setupResult = setup()
    
        handleSetupResult(instance, setupResult)
      }
    }
    
    function handleSetupResult (instance, setupResult) {
      // TODO
      if (typeof setupResult === 'object') {
        instance.setupState = setupResult
      }
      finishComponentSetup(instance)
    }
    
    function finishComponentSetup (instance) {
      const Component = instance.type
      if (Component.render) {
        instance.render = Component.render
      }
    
    }
    
  • createApp.ts

    import {createVNode} from "./vNode";
    import {render} from "./renderer";
    
    export function createApp (rootComponent) {
      return {
        // 接收一个element实例作为根容器,整体入口
        mount (rootContainer) {
          // 先转vNode  component -> vNode
          // 所有逻辑操作都会基于vNode做处理
          const vNode = createVNode(rootComponent)
          render(vNode, rootContainer)
        }
      }
    }
    
    
  • h.ts

    import { createVNode } from './vNode'
    export function h (type, props?, children?) {
      return createVNode(type, props, children)
    }
    
  • index.ts

    export { createApp } from './createApp'
    export { h } from './h'
    
  • renderer.ts

    import {createComponentInstance, setupComponent} from "./component";
    
    export function render (vNode, container) {
      patch(vNode, container)
    }
    
    function patch (vNode, container) {
      // TODO 判断vNode是不是element
      if (typeof vNode.type === 'string') {
        processElement(vNode, container)
      } else {
        // 去处理组件
        processComponent(vNode, container)
      }
    }
    
    function processComponent (vNode, container) {
      mountComponent(vNode, container)
    }
    
    function mountComponent (vNode, container) {
      // 与mountElement同理,创建并返回一个组件对象
      const instance = createComponentInstance(vNode)
    
      // 处理setup中的信息(例如:实现代理this)
      setupComponent(instance)
      // 第一次渲染App组件的时候会执行
      setupRenderEffect(instance, vNode, container)
    }
    
    function processElement (vNode, container) {
      mountElement(vNode, container)
    }
    
    function mountElement(vNode, container) {
      const el = document.createElement(vNode.type)
      // 处理子节点 - 如果是string类型直接赋值给DOM元素的textContent属性
      if (typeof vNode.children === 'string') {
        el.textContent = vNode.children
      }
      // 将DOM添加到对应容器中
      container.appendChild(el)
    }
    
    // 第一次渲染App组件的时候会执行,并且将render函数的this绑定为创建的代理对象
    function setupRenderEffect (instance, vNode, container) {
      // instance.render 来自于 finishComponentSetup 方法,就是组件的render方法
      // 绑定this,让render中的this指向创建的代理对象
      const subTree = instance.render.call(instance.proxy)
      // vNode -> patch
      // vNode -> element -> mountElement
      patch(subTree, container)
    
      // subTree指的就是class="root"的根节点
      // 子元素处理完成之后
      vNode.el = subTree.el
    }
    
    
  • vNode.ts

    export function createVNode (type, props?, children?) {
      const vNode = {
        type,
        props,
        children,
        el: null
      }
      return vNode
    }
    

初始化流程分析 - 可以打断点查看

  • 创建根容器 - const rootContainer = document.querySelector('#app')

  • 调用createApp并传入App组件,最后调用mount将app实例挂载到容器中 - createApp(App).mount(rootContainer)

  • 调用mount方法,实际就是整个系统的入口

  • 根据传入的App创建对应的虚拟DOM,调用render方法(传入虚拟DOM和跟容器)

  • 调用patch打补丁(或者叫挂载) - patch(vNode, container)

  • 判断虚拟DOM type 属性的类型(虚拟DOM的组成在createVNode中查看),如果是 string,那么它是一个普通HTML元素;否则它就是一个组件,调用processComponent 来处理组件 - processComponent(vNode, container)

  • 调用mountComponent(vNode, container)

  • 根据虚拟DOM创建组件实例 - createComponentInstance(vNode)

  • 调用setupComponent处理setup中的信息 - setupComponent(instance)

  • 调用setupStatefulComponent,调用组件对象的setup方法拿到返回值

  • 将返回值赋值给组件实例的setupState属性

  • 调用finishComponentSetup,将组件的render方法添加到组件实例上

  • 调用setupRenderEffect,他会调用组件实例的render方法返回组件对应的虚拟DOM,然后调用patch方法处理App组件的子元素

  • 后面就是处理element类型的元素,目前只关注component类型