全局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,其作用相当于Vue2的new 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.ts的createApp函数中重写了app.mount方法,所以App的mount入口在这里:
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.ts的createAppAPI中,其主要内容为,将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}