vue3源码浅析:createApp

1,016 阅读5分钟

createApp是vue3的启动函数,返回一个应用实例

对于createApp函数执行过程,我们从一个简单的例子开始

一个最简单的例子

<div class="" id="app">
    {{obj.name}}
</div>
<script>
const {
    createApp
} = Vue

const app = createApp({
    data(){
        return{
            obj:{name:'zhangsan'}
        }
    }
}).mount('#app')
</script>

先看下执行时候的调用栈,从下往上执行,知道了调用过程

createApp().mount('#app')
功能说明得到app实例,提供了应用api,各api能实现链式操作数据响应式,获得vNode,patch成DOM(diff算法)
调用栈

本文先看看createApp()的执行,初步看看里面每个函数都干了些什么

createApp

createApp返回一个提供应用上下文的应用实例。应用实例挂载的整个组件树共享同一个上下文。

你可以在 createApp 之后链式调用其它方法,这些方法可以在应用 API 中找到

实现链式调用的方法很巧妙,从源码中看createApp只是用来包装app实例的,一旦执行createApp内部的app实例就被创建并返回

export const createApp = ((...args) => {
  const app = ensureRenderer().createApp(...args)
  ...
  return app
})

createApp是整个vue3开始的入口,该方法完成两个功能:

  1. 得到app应用实例:执行createApp方法,返回app应用实例,app则是通过ensureRenderercreateApp方法得到
  2. 扩展mount方法

想要知道初始化都干了什么,接着看ensureRenderer

//** runtime-dom/src/index.ts */

export const createApp = ((...args) => {
  const app = ensureRenderer().createApp(...args)

  const { mount } = app
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    const container = normalizeContainer(containerOrSelector)
    if (!container) return
    const component = app._component
    if (!isFunction(component) && !component.render && !component.template) {
      component.template = container.innerHTML
    }
    // clear content before mounting
    container.innerHTML = ''
    const proxy = mount(container)
    if (container instanceof Element) {
      container.removeAttribute('v-cloak')
      container.setAttribute('data-v-app', '')
    }
    return proxy
  }

  return app
}) as CreateAppFunction<Element>

ensureRenderer

此函数为单例模式,有renderer方法则直接返回,没有则通过createRenderer创建

//** runtime-dom/src/index.ts */
const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps)

// lazy create the renderer - this makes core renderer logic tree-shakable
// in case the user only imports reactivity utilities from Vue.
let renderer: Renderer<Element> | HydrationRenderer

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

对于renderer方法,他又干了些什么,可以从其类型声明接口找到答案

export interface Renderer<HostElement = RendererElement> {
  render: RootRenderFunction<HostElement>
  createApp: CreateAppFunction<HostElement>
}

在初始化的时候,很显然renderer方法是不存在的,那么将会执行createRenderer方法

createRenderer(自定义渲染函数)

createRenderer 函数接受两个泛型参数: HostNodeHostElement,对应于宿主环境中的 Node 和 Element 类型。

在runtime-dom中,HostNode 将是 DOM Node 接口,HostElement 将是 DOM Element 接口。

createRenderer 函数,从源码的注释上可以知道,这是一个可以提供给用户自定义渲染器的方法。

createRenderer 函数的返回值对象必须要有rendercreateApp方法,而这两个方法是由baseCreateRenderer提供的

//** runtime-core/src/renderer.ts */

/**
 * The createRenderer function accepts two generic arguments:
 * HostNode and HostElement, corresponding to Node and Element types in the
 * host environment. For example, for runtime-dom, HostNode would be the DOM
 * `Node` interface and HostElement would be the DOM `Element` interface.
 *
 * Custom renderers can pass in the platform specific types like this:
 *  自定义渲染器可以像这样传入特定平台的类型:
 *
 * ``` js
 * const { render, createApp } = createRenderer<Node, Element>({
 *   patchProp,
 *   ...nodeOps
 * })
 * ```
 */
export function createRenderer<
  HostNode = RendererNode,
  HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
  return baseCreateRenderer<HostNode, HostElement>(options)
}

createRenderer 函数接收一个参数,该参数定义了宿主环境中的 Node 和 Element 类型的渲染方式

下图为vue3中createRenderer 函数rendererOptions参数默认方法

rendererOptions类型声明接口

//** runtime-dom/src/index.ts */
//vue3默认的的渲染方式
import { nodeOps } from './nodeOps'
import { patchProp, forcePatchProp } from './patchProp'
const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps)

//** runtime-core/src/renderer.ts */
export interface RendererOptions<
  HostNode = RendererNode,
  HostElement = RendererElement
> {
  patchProp(
    el: HostElement,
    key: string,
    prevValue: any,
    nextValue: any,
    isSVG?: boolean,
    prevChildren?: VNode<HostNode, HostElement>[],
    parentComponent?: ComponentInternalInstance | null,
    parentSuspense?: SuspenseBoundary | null,
    unmountChildren?: UnmountChildrenFn
  ): void
  forcePatchProp?(el: HostElement, key: string): boolean
  insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void
  remove(el: HostNode): void
  createElement(
    type: string,
    isSVG?: boolean,
    isCustomizedBuiltIn?: string
  ): HostElement
  createText(text: string): HostNode
  createComment(text: string): HostNode
  setText(node: HostNode, text: string): void
  setElementText(node: HostElement, text: string): void
  parentNode(node: HostNode): HostElement | null
  nextSibling(node: HostNode): HostNode | null
  querySelector?(selector: string): HostElement | null
  setScopeId?(el: HostElement, id: string): void
  cloneNode?(node: HostNode): HostNode
  insertStaticContent?(
    content: string,
    parent: HostElement,
    anchor: HostNode | null,
    isSVG: boolean
  ): HostElement[]
}

baseCreateRenderer

baseCreateRenderer方法定义了vue整个渲染过程,最终返回 render hydrate createApp 3个函数,

render方法:vnode转变为真实DOM

createApp方法:app实例创建的工厂函数,接收render方法,hydrate 为可选参数,ssr 的场景下会用到

//** runtime-core/src/renderer.ts */
function baseCreateRenderer(
  options: RendererOptions,
  createHydrationFns?: typeof createHydrationFunctions
): any {

  const {
    insert: hostInsert,
    remove: hostRemove,
    patchProp: hostPatchProp,
    forcePatchProp: hostForcePatchProp,
    createElement: hostCreateElement,
    createText: hostCreateText,
    createComment: hostCreateComment,
    setText: hostSetText,
    setElementText: hostSetElementText,
    parentNode: hostParentNode,
    nextSibling: hostNextSibling,
    setScopeId: hostSetScopeId = NOOP,
    cloneNode: hostCloneNode,
    insertStaticContent: hostInsertStaticContent
  } = options
  
  //...中间省略1800多行代码,内容为关于渲染器用到的函数声明
  const render: RootRenderFunction = (vnode, container) => {
    if (vnode == null) {
      if (container._vnode) {
        unmount(container._vnode, null, null, true)
      }
    } else {
        //初始化走这里
        //最终转换方法还是patch
      patch(container._vnode || null, vnode, container)
    }
    flushPostFlushCbs()
    container._vnode = vnode
  }
    //...
  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }
}

createAppAPI

createAppAPI方法是返回的是真正的createApp

当createApp被调用时,返回app实例,定义了实例方法,每个实例方法返回app实现链式操作

得到app实例后执行mount方法,调用render,通过patch将vnode转变为DOM

//** runtime-core/src/apiCreateApp.ts */
export function createAppAPI<HostElement>(
  render: RootRenderFunction,
  hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {
    if (rootProps != null && !isObject(rootProps)) {
      __DEV__ && warn(`root props passed to app.mount() must be an object.`)
      rootProps = null
    }

    // 创建默认APP配置
    const context = createAppContext()
    const installedPlugins = new Set()

    let isMounted = false
	// 定义app实例,相当于vue2的new Vue()
    const app: App = {
      _component: rootComponent as Component,
      _props: rootProps,
      _container: null,
      _context: context,

      get config() {
        return context.config
      },

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

      use() {},
      mixin() {},
      component() {},
      directive() {},

      mount(rootContainer: HostElement, isHydrate?: boolean): any {
        if (!isMounted) {
          const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
          )
          // store app context on the root VNode.
          // this will be set on the root instance on initial mount.
          vnode.appContext = context

          if (isHydrate && hydrate) {
            hydrate(vnode as VNode<Node, Element>, rootContainer as any)
          } else {
            render(vnode, rootContainer)
          }
          isMounted = true
          app._container = rootContainer

          return vnode.component!.proxy
        } else if (__DEV__) {
			...
        }
      },
          
      unmount() {},
      // ...
    }

    return app
  }
}