全局API

Vue3入口-createApp

相比较于Vue 2通过构造函数new Vue()的方式创建根Vue实例,Vue 3改为了通过调用 createApp返回一个应用实例:

1import { createApp } from 'vue'
2import App from './App.vue'
3
4createApp(App).mount('#app')

createApp源码位于@vue/runtime-dom/src/index.ts

1export const createApp = ((...args) => {
2  // 调用render.createApp生成app
3  const app = ensureRenderer().createApp(...args)
4
5  // 重新包装app的mount方法
6  const { mount } = app
7  app.mount = (containerOrSelector: Element | string): any => {
8    const container = normalizeContainer(containerOrSelector)
9    if (!container) return
10    const component = app._component
11    if (!isFunction(component) && !component.render && !component.template) {
12      component.template = container.innerHTML
13    }
14    // clear content before mounting
15    container.innerHTML = ''
16    const proxy = mount(container)
17    container.removeAttribute('v-cloak')
18    container.setAttribute('data-v-app', '')
19    return proxy
20  }
21
22  return app
23}) as CreateAppFunction<Element>

ensureRenderer

1// lazy create the renderer - this makes core renderer logic tree-shakable
2// in case the user only imports reactivity utilities from Vue.
3let renderer: Renderer<Element> | HydrationRenderer
4
5let enabledHydration = false
6
7function ensureRenderer() {
8  return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
9}

createRenderer位于@vue/runtime-core/renderer.ts,调用了同文件的baseCreateRenderer,其代码如下:

1function baseCreateRenderer(
2  options: RendererOptions,
3  createHydrationFns?: typeof createHydrationFunctions
4): any {
5  //... 无关代码省略
6
7  return {
8    render,
9    hydrate,
10    createApp: createAppAPI(render, hydrate)
11  }
12}

因为在createApp中只使用到了render.createApp,所以我们把无关代码省略,可以看到render.createApp是由createAppAPI生成的,代码位于@vue/runtime-core/apiCreateApp.ts第114行,其作用是返回一个createApp方法,用于生成一个AppContext和一个App,其作用相当于Vue2new Vue(),代码如下:

1export function createAppAPI<HostElement>(
2  render: RootRenderFunction,
3  hydrate?: RootHydrateFunction
4): CreateAppFunction<HostElement> {
5  return function createApp(rootComponent, rootProps = null) {
6    // 确保rootProps只能是null或者Object
7    if (rootProps != null && !isObject(rootProps)) {
8      rootProps = null
9    }
10
11    const context = createAppContext()
12    const installedPlugins = new Set()
13
14    let isMounted = false
15
16    const app: App = (context.app = {
17      // ... app对象内容省略
18    })
19
20    return app
21  }
22}

App的mount过程

由于在@vue/runtime-dom/src/index.tscreateApp函数中重写了app.mount方法,所以Appmount入口在这里:

1// @vue/runtime-dom/src/index.ts 61行
2app.mount = (containerOrSelector: Element | string): any => {
3  // 如果是选择器,则返回对应的dom
4  const container = normalizeContainer(containerOrSelector)
5  if (!container) return
6  
7  // clear content before mounting
8  container.innerHTML = ''
9
10  const proxy = mount(container)
11  
12  container.removeAttribute('v-cloak')
13  container.setAttribute('data-v-app', '')
14  return proxy
15}

主要做了以下的操作,将选择器转行成DOM,将container清空,调用app原来的mount方法,并对DOM的属性做了一些操作

app原来的mount方法位于@vue/runtime-core/apiCreateApp.tscreateAppAPI中,其主要内容为,将App组件内容生成VNode,将vnode.appContext设置上,调用render(vnode, rootContainer)渲染组件,将闭包里的isMounted设置成true,将rootContainer设置给app._container,将vnode.component!.proxy返回。

1function createApp(rootComponent, rootProps = null) {
2  // 其他内容省略
3  const context = createAppContext()
4  let isMounted = false
5
6  const app: App = (context.app = {
7    // ... 其他app对象内容省略
8    mount(rootContainer: HostElement, isHydrate?: boolean): any {
9      if (!isMounted) {
10        const vnode = createVNode(
11          rootComponent as ConcreteComponent,
12          rootProps
13        )
14        // store app context on the root VNode.
15        // this will be set on the root instance on initial mount.
16        vnode.appContext = context
17
18        render(vnode, rootContainer)
19
20        isMounted = true
21        app._container = rootContainer
22        
23        return vnode.component!.proxy
24      }
25      // else if 内容为开发环境报警
26    },
27  })
28}