前言
这是vue3系列源码的第一章,使用的vue3版本是3.2.45
createApp的流程
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
这一段是我们在main文件中用到的。今天我们看看这个createApp到底是怎么过来的。
这里,我们先打个断点,看一下,我们穿进去的App参数到底长什么样子。
这是我们的App.vue文件
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
<script setup>
import { onMounted } from 'vue';
import HelloWorld from './components/HelloWorld.vue'
onMounted(() => console.log('new mounted'))
</script>
我们的template部分被转成了render函数,下面是定义的setup函数。
我们今天的重点还是看一下createApp的流程。
在runtime-dom部分中,首先export出了一个createApp函数,这个函数主要的工作就是定义了一个app的对象,然后return出来。
也就是说,我们通过这个函数最终得到的是一个app的对象,那么这个对象是怎么来的,都有什么。
这个函数的第一行
const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
...
ensureRenderer()
先看看ensureRender函数
function ensureRenderer() {
return (
renderer ||
(renderer = createRenderer(rendererOptions))
)
}
这个函数其实就是reutrn了由createRender函数返回的对象
那么再看看cerateRenderer函数
function createRenderer(options) {
return baseCreateRenderer(options)
}
这个函数是定义在runtime-core中的,也是直接返回了一个由baseCreateRenderer函数返回的对象,
接着看看baseCreateRenderer函数
baseCreateRenderer
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 ...
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
}
这个函数很重要,相当相当的重要,这个函数在源码中占据了两千多行。
我们在后面的文章里面会很多很多次提到这个函数。
我们先看一下传进来的option参数。
其实主要是一些操作dom的方法,这一片文章里,我们还用不着,后面讲源码实际渲染的时候就能用到了。
首先是getGlobalThis
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。
在上文是这么定义的:
const rendererOptions = /*#__PURE__*/ extend({ patchProp }, nodeOps)
我们看看这个renderOptions到底是个什么东西。
里面包含了一些方法,这些方法都是操作虚拟dom的。
option解构完了之后,后面又是一些方法的定义,这里我们先不用管。
这个函数最终返回了一个对象,包含了createApp这个属性。
所以,我们第一步ensureRenderer函数其实就是通过层层转包,最终返回一个带有createApp属性的对象。
那么ensoureRenderer函数差不多就这样了。
createApp
接下来,我们再看看createApp的过程。
上面说到这里的createApp其实是createAppAPI这个函数。
createAppAPI
function createAppAPI(render, hydrate) {
return function createApp(rootComponent, rootProps = null) {
...
大家看,又像套娃一样,createAPPAPI函数原来又是返回一个createApp函数。
这已经是createApp这个函数名第三次出现,每次出现还都不是同一个函数。
不要慌,那我们就继续深入这个createApp函数中去。
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
}
这个函数的参数rootComponent就是我们在最开始查看的的App.vue文件被转成的那个对象。
首先看一下createAppContext这个函数
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等。
这里注意一下,我们传入的参数,也就是我们的App.vue文件,在这里面是放在了_component字段。
而且这里的mount其实就是我们在main.js文件中调用的那个mount方法的核心。
关于mount部分,我们下一篇文章会讲到。
那么到了这里,我们其实就看完了runtime-core部分的createApp函数。
其实就是返回了一个将要包含各种全局数据的app对象。
app
让我们再回到runtime-dom中的createApp函数中。
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对象。
流程图
graph TB
subgraph runtime-dom
direction TB
subgraph createAppdom[createApp]
direction TB
appdom("app = ensureRenderer().createApp(...args);") --> ensureRenderer -- return --> createRenderer
end
main("createApp(App)") -- return--> appdom
end
subgraph runtime-core
direction TB
createRenderer[createRenderer] -- return --> baseCreateRenderer
subgraph baseCreateRenderer["baseCreateRenderer(options, createHydrationFns)"]
direction TB
createAppAPI("createAppAPI(render, hydrate)") -- return -->
createApp("fun createApp(rootComponent, rootProps)") --return--> app
subgraph app["obj app"]
direction TB
oteher("
use
mixin
component
directive
unmount
provid
mount")
end
end
end