前言
这是vue3系列源码的第一章,使用的vue3版本是3.2.45
createApp的流程
js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
这一段是我们在main文件中用到的。今天我们看看这个createApp到底是怎么过来的。
在runtime-dom
部分中,首先export出了一个createApp
函数,这个函数主要的工作就是定义了一个app的对象,然后return出来。 也就是说,我们通过这个函数最终得到的是一个app的对象,那么这个对象是怎么来的,都有什么。
这个函数的第一行
js
const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
...
ensureRenderer()
先看看ensureRender函数
js
function ensureRenderer() {
return (
renderer ||
(renderer = createRenderer(rendererOptions))
)
}
这个函数其实就是reutrn了由createRender函数返回的对象
那么再看看cerateRenderer函数
js
function createRenderer(options) {
return baseCreateRenderer(options)
}
这个函数是定义在runtime-core
中的,也是直接返回了一个由baseCreateRenderer函数返回的对象, 接着看看baseCreateRenderer函数
js
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
这个函数最终返回了一个对象,包含了createApp这个属性。
所以,我们第一步ensureRenderer函数其实就是通过层层转包,最终返回一个带有createApp属性的对象。
baseCreateRenderer
那么我们再回头看看,baseCreateRenderer这个函数都干了啥。
js
function baseCreateRenderer(options, createHydrationFns){
const target = getGlobalThis()
const {
insert: hostInsert,
remove: hostRemove,
patchProp: hostPatchProp,
createElement: hostCreateElement,
createText: hostCreateText,
createComment: hostCreateComment,
setText: hostSetText,
setElementText: hostSetElementText,
parentNode: hostParentNode,
nextSibling: hostNextSibling,
setScopeId: hostSetScopeId = NOOP,
insertStaticContent: hostInsertStaticContent
} = options
const patch = () => {...}
const render = () => {...}
const ...
}
首先是getGlobalThis
js
const getGlobalThis = () => {
return (_globalThis ||
(_globalThis =
typeof globalThis !== 'undefined'
? globalThis
: typeof self !== 'undefined'
? self
: typeof window !== 'undefined'
? window
: typeof global !== 'undefined'
? global
: {}));
}
这个函数其实就是用来获取全局对象的,我们在浏览器运行,得到的其实就是window
对象。
getGlobalThis
函数下面是一个对option
对象的解构。
这个option对象是怎么来的呢。
是在上文中的ensureRenderer
函数的闭包中出现的,传进来的值是rendererOptions
。
在上文是这么定义的:
js
const rendererOptions = /*#__PURE__*/ extend({ patchProp }, nodeOps)
我们看看这个renderOptions到底是个什么东西。
里面包含了一些方法,这些方法都是操作虚拟dom的。
option解构完了之后,后面又是一些方法的定义,这里我们先不用管。
那么ensoureRenderer
函数差不多就这样了。
createApp
接下来,我们再看看createApp的过程。
上面说到这里的createApp
其实是createAppAPI
这个函数。
createAppAPI
js
function createAppAPI(render, hydrate) {
return function createApp(rootComponent, rootProps = null) {
...
大家看,又像套娃一样,createAPPAPI函数原来又是返回一个createApp
函数。
这已经是createApp
这个函数名第三次出现,每次出现还都不是同一个函数。
不要慌,那我们就继续深入这个createApp
函数中去。
js
return function createApp(rootComponent, rootProps = null) {
const context = createAppContext()
const installedPlugins = new Set()
let isMounted = false
const app: App = (context.app = {
_uid: uid++,
_component: rootComponent as ConcreteComponent,
_props: rootProps,
_container: null,
_context: context,
_instance: null,
version,
use(){...},
mixin(){...},
mount(){...}
...
}
return app
}
首先看一下createAppContext
这个函数
js
function createAppContext(): AppContext {
return {
app: null as any,
config: {
isNativeTag: NO,
performance: false,
globalProperties: {},
optionMergeStrategies: {},
errorHandler: undefined,
warnHandler: undefined,
compilerOptions: {}
},
mixins: [],
components: {},
directives: {},
provides: Object.create(null),
optionsCache: new WeakMap(),
propsCache: new WeakMap(),
emitsCache: new WeakMap()
}
}
这个对象最终返回了一个vue全局上下文,里面将会包含全局的一些数据。这个对象下一步被存在了app对象的_context
字段中。
那么在app对象中,这里定义了一些属性和方法,其中方法有我们常用的use``mixin``mount
等。
而且这里的mount
其实就是我们在main.js
文件中调用的那个mount
方法的核心。 关于mount
部分,我们下一篇文章会讲到。
那么到了这里,我们其实就看完了runtime-core
部分的createApp
函数。 其实就是返回了一个将要包含各种全局数据的app对象。
app
让我们再回到runtime-dom
中的createApp
函数中。
js
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) {
// __UNSAFE__
// Reason: potential execution of JS expressions in in-DOM template.
// The user must make sure the in-DOM template is trusted. If it's
// rendered by the server, the template should not contain any user data.
component.template = container.innerHTML
}
// clear content before mounting
container.innerHTML = ''
const proxy = mount(container, false, container instanceof SVGElement)
if (container instanceof Element) {
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
}
return proxy
}
return app
})
这个函数的剩下部分其实就是对得到的app.mount函数进行了装饰,核心还是createAppAPI
函数中定义的mount
方法。
那么,到此,我们差不多就弄明白了createApp这个函数的作用,核心就是返回了一个包含了各种全局数据的app对象。