Vue3中初始化一个程序:
将App
(根组件)作为createApp
的参数,返回一个app实例
(对象),再调用实例中的mount
方法,将应用程序渲染
到 id 为 'app' 的 HTML 元素中
js
import {createApp} from 'vue'
import App from './App'
const app = createApp(App)
app.mount('#app')
那么这个createApp
是从哪里创建的呢?其实它是由渲染器创建的,我们之前说过:创建渲染器的函数需要接受不同参数以具备在不同平台渲染的能力,渲染器会返回渲染函数处理VNode,同时他也会返回createApp.
js
//渲染器返回
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
createAppAPI
里直接返回createApp
,他又会返回一个app实例
。
可以看到不仅仅是返回了一个app,还把他赋值给了context中的app属性,createAppContext
返回context上下文对象(可以把它看出一个全局变量),可以看出其中还包含着mixin,指令等内容,也就是一个容器。
现在就不难看出app实例
中的方法是为了将对应的内容注册到context
容器中。
js
export 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()
}
}
js
function createAppAPI(render, hydrate) {
return function createApp(rootComponent, rootProps = null) {
//省略代码
const context = createAppContext()
const app = (context.app = {
use() {
//省略具体代码
},
mixin(mixin: ComponentOptions) {
if (__FEATURE_OPTIONS_API__) {
if (!context.mixins.includes(mixin)) {
context.mixins.push(mixin)
} else if (__DEV__) {
warn(
'Mixin has already been applied to target app' +
(mixin.name ? `: ${mixin.name}` : '')
)
}
} else if (__DEV__) {
warn('Mixins are only available in builds supporting Options API')
}
return app
},
component() {
//省略具体代码
},
directive() {
//省略具体代码
},
mount() {
//省略具体代码
},
unmount() {
//省略具体代码
},
provide() {
//省略具体代码
}
})
return app
}
}
补充一点:
createAppAPI
内使用了闭包和柯里化
,实际上柯里化通常需要使用闭包。createApp函数并没有接收render作为参数,但是却可以使用render函数,因为这里形成了一个闭包。
柯里化(Currying)是一种函数式编程的技巧,它将一个接受多个参数的函数转化为一系列接受单个参数的函数,举个例子
js
function sum(a, b, c) {
console.log(a + b + c)
}
sum(1,2,3)
//柯里化
function sum(a) {
return function (b) {
return function (c) {
console.log(a + b + c)
}
}
}
sum(1)(2)(3)
经过柯里化可以逐个传递参数,每次调用都会返回一个新的函数,直到所有参数都被传递,最后的调用将返回最终结果。这使得函数调用变得更加灵活和可组合
这里直接再调用app实例中的mount方法,mount的具体执行流程是执行createVNode
生成VNode,在把生成的VNode作为渲染函数的参数,接在通过递归渲染将会完成整个应用的渲染
但是我们需要对mount函数进行重写
mount接受的参数是一个容器
,但是在Web平台上它是一个DOM对象;而在其他平台(比如Weex和小程序)上它可以是其他类型的值。所以使用normalizeContainer
标准化容器,再将传入标准化容器的mount函数返回
再回到createApp
,它其实是@vue/runtime-dom
导出的。其中还是使用到了渲染器生成的createApp
函数,再获取app实例,此时重写app中的mount方法,并把修改后的app当成@vue/runtime-dom
导出的createApp
返回值
js
import {createApp} from '@vue/runtime-dom'
js
export 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
//如果组件对象没有定义re∩der函数和template模板,则取容器的innerHTML作为组件模板内容
if (!isFunction(component) && !component.render && !component.template) {
component.template = container.innerHTML
}
// 挂载前清除
container.innerHTML = ''
const proxy = mount(container, false, container instanceof SVGElement)
//这里返回的是传入标准化容器的mount函数
return proxy
}
return app
}