【vue3源码】十一、初始vue3中的渲染器
在介绍渲染器之前。我们先简单了解下渲染器的作用是什么。
渲染器最主要的任务就是将虚拟DOM渲染成真实的DOM对象到对应的平台上,这里的平台可以是浏览器DOM平台,也可以是其他诸如canvas的一些平台。总之vue3的渲染器提供了跨平台的能力。
渲染器的生成
当使用createApp
创建应用实例时,会首先调用一个ensureRenderer
方法。
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
// ...
return app
}) as CreateAppFunction<Element>
ensureRenderer
函数会返回一个渲染器renderer
,这个renderer
是个全局变量,如果不存在,会使用createRenderer
方法进行创建,并将创建好的renderer
赋值给这个全局变量。
function ensureRenderer() {
return (
renderer ||
(renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
)
}
createRenderer
函数接收一个options
参数,至于这个options
中是什么,这里我们暂且先不深究。createRenderer
函数中会调用baseCreateRenderer
函数,并返回其结果。
export function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}
至此,我们就找到了真正创建渲染器的方法baseCreateRenderer
。当我们找到baseCreateRenderer
的具体实现,你会发现这个函数是十分长的,单baseCreateRenderer
这一个函数就占据了2044行代码,其中更是声明了30+个函数。
在此我们先不用关心这些函数的作用,在后续介绍组件加载及更新过程时,你会慢慢了解这些函数。
接下来我们继续看渲染器对象的结构。
渲染器
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
在baseCreateRenderer
最后返回了一个对象,这个对象包含了三个属性:render
(渲染函数)、hydrate
(同构渲染)、createApp
。这里的createApp
是不是很熟悉,在createApp
中调用ensureRenderer
方法后面会紧跟着调用了createApp
函数:
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
// ...
return app
}) as CreateAppFunction<Element>
注意这里不要两个createApp
混淆了。渲染器中的createApp
并不是我们平时使用到的createApp
。当我们调用createApp
方法进行创建实例时,会调用渲染器中的createApp
生成app
实例。
接下来我们来看下渲染器中的createApp
。首先createApp
方法通过一个createAppAPI
方法生成,这个方法接收渲染器中的render
及hydrate
:
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
// ...
}
}
createAppAPI
函数会返回一个闭包函数createApp
。这个createApp
就是通过ensureRenderer().createApp(...args)
调用的方法了。接下来看createApp
的具体实现:
createApp
完整代码
function createApp(rootComponent, rootProps = null) {
if (!isFunction(rootComponent)) {
rootComponent = {
...rootComponent }
}
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
const app: App = (context.app = {
_uid: uid++,
_component: rootComponent as ConcreteComponent,
_props: rootProps,
_container: null,
_context: context,
_instance: null,
version,
get config() {
return context.config
},
set config(v) {
if (__DEV__) {
warn(
`app.config cannot be replaced. Modify individual options instead.`
)
}
},
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