创建 Vue 应用实例,是一个项目的开始,这篇文章看看 createApp 内部都发生了什么。当然如果你对 Vue3 响应式系统感兴趣,也可以先看看这两篇文章:
Vue3源码 | 深入理解响应式系统上篇-reactive
Vue3源码 | 深入理解响应式系统下篇-effect
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 过程。
评论