Day 3 - Vue3 源码学习 - Vue 初始化流程

196 阅读2分钟

应用程序示例创建过程 createApp()

如何创建实例?

在 todomvc 中调用 createApp 方法,并且把相应的配置信息传进去。

const { createApp, ... } = Vue

createApp({
  setup () {
    ...
    return { ... }
  },

  directives: {
    'todo-focus': (el, { value }) => {
      if (value) {
        el.focus()
      }
    }
  }
}).mount('#app')

runtime-dom/src/index.ts 文件中定义了 createApp,createApp 方法通过 ensureRenderer().createApp() 创建实例。

export const createApp = ((...args) => {
  //调用渲染器创建 app
  const app = ensureRenderer().createApp(...args)

  ...

  const { mount } = app
  //覆盖 app 的 mount 方法(分支问题)
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    ...
  }

  return app
}) as CreateAppFunction<Element>

同样在 runtime-dom/src/index.ts 文件中定义了 ensureRenderer(),这个方法返回一个渲染器。ensureRenderer 方法又会调用 createRenderer 创建渲染器。

function ensureRenderer() {
  return (
    renderer ||
    (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
  )
}

createRenderer 再调用 baseCreateRenderer,该方法在 runtime-core/src/renderer.ts 中,最终返回渲染器,这也是一个超级长的方法。返回一个对象,对象包含了 createApp 方法等。createApp 由一个工厂方法 createAppAPI 创建。

export function createRenderer<
  HostNode = RendererNode,
  HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
  return baseCreateRenderer<HostNode, HostElement>(options)
}
function baseCreateRenderer(
  options: RendererOptions,
  createHydrationFns?: typeof createHydrationFunctions
): any {
  ...

  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }
}

实例长什么样子?

createAppAPI 工厂方法位于 runtime-core/src/apiCreateApp.ts 文件中,返回 createApp 方法,该方法返回 app 对象,app 中有我们熟悉的 use、mixin、mount 等等。

export function createAppAPI<HostElement>(
  render: RootRenderFunction,
  hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {
    ...

    const app: App = (context.app = {
      ...

      version,

      get config() {
        return context.config
      },

      set config(v) {
        if (__DEV__) {
          warn(
            `app.config cannot be replaced. Modify individual options instead.`
          )
        }
      },

      use(plugin: Plugin, ...options: any[]) {
        ...
        return app
      },

      mixin(mixin: ComponentOptions) {
        ...
        return app
      },

      component(name: string, component?: Component): any {
        ...
        return app
      },

      directive(name: string, directive?: Directive) {
        ...
        return app
      },

      mount(
        rootContainer: HostElement,
        isHydrate?: boolean,
        isSVG?: boolean
      ): any {
        ...
      },

      unmount() {
        ...
      },

      provide(key, value) {
        ...

        return app
      }
    })

    ...

    return app
  }
}

挂载过程 app.mount()

挂载过程是怎么样的?

  1. 创建根节点的 vnode;
  2. 执行渲染,把第一步创建的 vnode 传给 patch 转化成真实 dom,再追加到宿主元素中,宿主元素为 app.mount() 传入的元素;
export function createAppAPI<HostElement>(
  render: RootRenderFunction,
  hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {
    ...

    const app: App = (context.app = {
      ...

      mount(
        rootContainer: HostElement,
        isHydrate?: boolean,
        isSVG?: boolean
      ): any {
        //保证只挂载一次
        if (!isMounted) {
          //创建虚拟节点
          const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
          )
          
          ...

          if (isHydrate && hydrate) {
            hydrate(vnode as VNode<Node, Element>, rootContainer as any)
          } else {
            //这里的 render 是通过参数传进来的
            render(vnode, rootContainer, isSVG)
          }
          isMounted = true
          ...
        } else if (__DEV__) {
          ...
        }
      },

      ...
    })

    ...

    return app
  }
}
function baseCreateRenderer(
  options: RendererOptions,
  createHydrationFns?: typeof createHydrationFunctions
): any {
  ...
  
  const render: RootRenderFunction = (vnode, container, isSVG) => {
    if (vnode == null) {
      if (container._vnode) {
        unmount(container._vnode, null, null, true)
      }
    } else {
      //将虚拟节点转换成真实节点,并追加到 container 上
      //这里的 container 是 app.mount('#app') 中的 #app
      patch(container._vnode || null, vnode, container, null, null, null, isSVG)
    }
    ...
  }

  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }
}

挂载的调用堆栈

image.png

Vue3 初始化流程

初始化流程调用栈

image.png

初始化流程图

image.png

setupRenderEffect():安装渲染函数副作用,除了首次 patch之外,还会定义更新机制,以后如果有数据变化会继续更新。