「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战」
创建Vue应用实例,是一个项目的开始,这篇文章看看createApp内部都发生了什么。当然如果你对Vue3响应式系统感兴趣,也可以先看看这两篇文章:
VUE3使用
这里看下Vue3是如何进行应用初始化。
// vue3 应用初始化
import { createApp } from 'vue'
import App from './app'
const app = createApp(App)
app.mount('#app')
// 组件渲染和未捕获错误配置的处理程序
app.config.errorHandler = (err, vm, info) => {}
// 添加全局属性
app.config.globalProperties.$http = () => {} // 这里相当于挂载到Vue2的 Vue.prototype
// 指定一种方法识别Vue之外定义的自定义元素
app.config.isCustomElement = tag => tag.startsWith('ion-')
// 注册组件
app.component('my-component', {})
// 检索组件
const MyComponent = app.component('my-component')
// 注册指令
app.directive('my-directive',{})
// 设置一个可以注入到应用程序内所有组件中的值。组件应使用inject来接收提供的值。
app.provide('user', 'administrator')
// 卸载应用程序
app.unmount()
// 安装vue插件
import MyPlugin from './plugins/MyPlugin'
app.use(MyPlugin)
...
复制代码
Vue3跟Vue2初始化区别不大,都是创建应用实例,然后挂载到DOM节点上。这里也可以看出,Vue是通过JS渲染页面,跟传统页面DOM直出的方式是不一样。DOM直出,简单说是,请求后返回的HTML页面是最终的呈现效果。
createApp流程
createApp
createApp用于创建应用实例,下面看看内部代码实现:
export const createApp = ((...args) => {
// 创建app实例
const app = ensureRenderer().createApp(...args)
const { mount } = app
// 重写mount方法
app.mount = (containerOrSelector: Element | string): any => {
// 标准化容器
const container = normalizeContainer(containerOrSelector)
if (!container) return
const component = app._component
// 组件不存在render函数和模板template,则使用container的innerHTML做为组件模板
if (!isFunction(component) && !component.render && !component.template) {
component.template = container.innerHTML
}
// 挂载前,清空容器的内容
container.innerHTML = ''
// 挂载容器
const proxy = mount(container)
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
return proxy
}
return app
}) as CreateAppFunction<Element>
复制代码
通过上面源码解析,我们可以看出createApp主要是干了两件事:
- 创建app实例,并返回该实例
- 重写mount方法
看完会存在两个主要疑问,ensureRenderer
是干啥用的?为什么要重写 mount
方法,而不直接使用呢?
ensureRenderer
用于创建渲染器,渲染器核心代码处于 runtime-core/src/renderer.ts
文件。
baseCreateRenderer// 可以看出render存在2种类型的渲染器
let renderer: Renderer<Element> | HydrationRenderer
// 延迟创建渲染器,当用户只是使用reactive响应库,可以做tree-shaking优化
function ensureRenderer() {
return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}
export function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}
// HydrationRenderer渲染器,也是只在调用的时候创建,方便做tree-shaking优化
export function createHydrationRenderer(
options: RendererOptions<Node, Element>
) {
return baseCreateRenderer(options, createHydrationFunctions)
}
复制代码
从分析可以看出:
- 存在2种类型的渲染器,它们都是基于
baseCreateRenderer
函数创建,此函数存在重载。 - 渲染器都是通过延迟创建,方便不使用的时候做
tree-shaking
。
baseCreateRenderer
这个函数是个大家伙,1800行左右的代码,包含了组件渲染的核心逻辑。这里只抽离挂载过程相关逻辑,涉及组件更新等其他渲染逻辑,留到后面再深入研究。
function baseCreateRenderer(
options: RendererOptions,
createHydrationFns?: typeof createHydrationFunctions
): any {
// 其他关于组件渲染逻辑以及hydrate相关这里省略
// 渲染函数
const render: RootRenderFunction = (vnode, container) => {
if (vnode == null) {
// 虚拟节点不存在,则销毁
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
// 虚拟节点存在,则更新或创建
patch(container._vnode || null, vnode, container)
}
flushPostFlushCbs()
// 缓存虚拟节点数据,作为已完成渲染的标识
container._vnode = vnode
}
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
}
复制代码
可以看出 baseCreateRenderer
主要实现了:
- 实现了组件渲染的创建、更新、卸载等核心逻辑(后续解读)
- 返回渲染函数,以及创建应用实例方法,当然还有
hydrate
。
createAppAPI
组件渲染核心部分后面看,这里看看创建实例的API实现。
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
// createApp接收2个参数,根组件和根组件的属性
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
}
// 创建一个上下文对象
const context = createAppContext()
const installedPlugins = new Set()
let isMounted = false
// 重写上下文对象的app属性
const app: App = (context.app = {
_component: rootComponent as Component,
_props: rootProps,
_container: null,
_context: context,
version,
// 暴露的应用实例的配置
get config() {
return context.config
},
set config(v) {
if (__DEV__) {
warn(
`app.config cannot be replaced. Modify individual options instead.`
)
}
},
// 插件的安装,可以看出,插件如果是对象,必须有install方法;如果是函数,则默认是安装方法
use(plugin: Plugin, ...options: any[]) {
if (installedPlugins.has(plugin)) {
__DEV__ && warn(`Plugin has already been applied to target app.`)
} else if (plugin && isFunction(plugin.install)) {
installedPlugins.add(plugin)
plugin.install(app, ...options)
} else if (isFunction(plugin)) {
installedPlugins.add(plugin)
plugin(app, ...options)
} else if (__DEV__) {
warn(
`A plugin must either be a function or an object with an "install" ` +
`function.`
)
}
return app
},
mixin(mixin: ComponentOptions) {
if (__FEATURE_OPTIONS_API__) {
if (!context.mixins.includes(mixin)) {
context.mixins.push(mixin)
}
}
return app
},
component(name: string, component?: PublicAPIComponent): any {
if (__DEV__) {
validateComponentName(name, context.config)
}
// 组件存在,则返回
if (!component) {
return context.components[name]
}
if (__DEV__ && context.components[name]) {
warn(`Component "${name}" has already been registered in target app.`)
}
// 不存在就注册
context.components[name] = component
return app
},
directive(name: string, directive?: Directive) {
if (__DEV__) {
validateDirectiveName(name)
}
if (!directive) {
return context.directives[name] as any
}
if (__DEV__ && context.directives[name]) {
warn(`Directive "${name}" has already been registered in target app.`)
}
context.directives[name] = directive
return app
},
mount(rootContainer: HostElement, isHydrate?: boolean): any {
if (!isMounted) {
// 根据根组件创建虚拟节点
const vnode = createVNode(rootComponent as Component, rootProps)
vnode.appContext = context
// HMR root reload
if (__DEV__) {
context.reload = () => {
render(cloneVNode(vnode), rootContainer)
}
}
render(vnode, rootContainer)
isMounted = true
app._container = rootContainer
// for devtools and telemetry
;(rootContainer as any).__vue_app__ = app
return vnode.component!.proxy
}
},
unmount() {
if (isMounted) {
render(null, app._container)
devtoolsUnmountApp(app)
}
},
provide(key, value) {
context.provides[key as string] = value
return app
}
})
return app
}
}
复制代码
从上面的代码,我们可以了解到createAppAPI主要实现了:
- 创建定义一个实例上下文context,包含属性和方法
- 重写扩展context.app方法,实现用户可以对上下文相关属性的自定义操作,也就是应用实例暴露的api实现,比如自定义指令、混入mixin、组件等提供用户自定义实现。
- 根据根组件和属性在
mount
方法中完成虚拟节点vNode
的转换,并通过render
喊完成渲染,关于渲染函数在baseCreateRender
已经说过。
总结
至此分析了createApp大致的流程,内部更细致的实现,后续再根据内容深入分析,这里再总结下整个过程。
- 执行
createApp
首先会创建渲染器,这里要注意的是存在2种渲染器类型,并且它们都是通过延迟创建的,主要目的是当用户只引用reactive响应式框架的时候,方便进行tree-shaking优化。且两种渲染器都是基于baseCreateRender
方法来实现。 baseCreateRender
函数执行后会返回render
渲染函数和createApp
方法,其中render
函数是组件创建、更新和卸载的主要核心逻辑实现。createApp则用于创建应用实例,进行应用实例的初始化。- createAppAPI用于生成默认的应用上下文
context
,这里定义了应用实例具备的属性和方法,并通过重写扩展context.app
属性,让用户能够进行对上下文的自定义操作,比如自定义组件、指令、mixin、插件安装等一系列操作。并存在mount方法完成将根组件转为虚拟节点vNode
,并通过render
函数完成对vNode
的渲染。
下一篇,我们将立足这篇文章,接着深入render函数内部,先看看vNode是什么,再瞧瞧DOM Diff过程。