【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
的具体实现:
<details>
<summary>createApp
残缺代码</summary>
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 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) } 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(name: string, component?: Component): 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, isSVG?: boolean ): any { if (!isMounted) { // #5571 if (__DEV__ && (rootContainer as any).__vue_app__) { warn( `There is already an app instance mounted on the host container.\n` + ` If you want to mount another app on the same host container,` + ` you need to unmount the previous app by calling \`app.unmount()\` first.` ) } const vnode = createVNode( rootComponent as ConcreteComponent, rootProps ) // store app context on the root VNode. // this will be set on the root instance on initial mount. vnode.appContext = context // HMR root reload if (__DEV__) { context.reload = () => { render(cloneVNode(vnode), rootContainer, isSVG) } } if (isHydrate && hydrate) { hydrate(vnode as VNode<Node, Element>, rootContainer as any) } else { render(vnode, rootContainer, isSVG) } isMounted = true app._container = rootContainer // for devtools and telemetry ;(rootContainer as any).__vue_app__ = app if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { app._instance = vnode.component devtoolsInitApp(app, version) } return getExposeProxy(vnode.component!) || vnode.component!.proxy } else if (__DEV__) { warn( `App has already been mounted.\n` + `If you want to remount the same app, move your app creation logic ` + `into a factory function and create fresh app instances for each ` + `mount - e.g. \`const createMyApp = () => createApp(App)\`` ) } }, unmount() { if (isMounted) { render(null, app._container) if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { app._instance = null devtoolsUnmountApp(app) } delete app._container.__vue_app__ } else if (__DEV__) { warn(`Cannot unmount an app that is not mounted.`) } }, provide(key, value) { if (__DEV__ && (key as string | symbol) in context.provides) { warn( `App already provides property with key "${String(key)}". ` + `It will be overwritten with the new value.` ) } context.provides[key as string | symbol] = value return app } }) if (__COMPAT__) { installAppCompatProperties(app, context, render) } return app}
</details>
这个createApp
函数与vue
提供的createApp
一样,都承受一个根组件参数和一个rootProps
(根组件的props
)参数。
首先,如果根组件不是办法时,会将rootComponent
应用解构的形式从新赋值为一个新的对象,而后判断rootProps
如果不为null
并且也不是个对象,则会将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}
而后调用createAppContext()
办法创立一个上下文对象。
export function createAppContext(): AppContext { return { app: null as any, config: { // 一个判断是否为原生标签的函数 isNativeTag: NO, performance: false, globalProperties: {}, // 自定义options的合并策略 optionMergeStrategies: {}, errorHandler: undefined, warnHandler: undefined, // 组件模板的运行时编译器选项 compilerOptions: {} }, // 存储全局混入的mixin mixins: [], // 保留全局注册的组件 components: {}, // 保留注册的全局指令 directives: {}, // 保留全局provide的值 provides: Object.create(null), // 缓存组件被解析过的options(合并了全局mixins、extends、部分mixins) optionsCache: new WeakMap(), // 缓存每个组件通过标准化的的props options propsCache: new WeakMap(), // 缓存每个组件通过标准化的的emits options emitsCache: new WeakMap() }}
而后申明了一个installedPlugins
汇合和一个布尔类型的isMounted
。其中installedPlugins
会用来存储应用use
装置的plugin
,isMounted
代表根组件是否曾经挂载。
const installedPlugins = new Set()let isMounted = false
紧接着申明了一个app
变量,这个app
变量就是app
实例,在创立app
的同时会将app
增加到上下文中的app
属性中。
const app: APP = (context.app = { //... })
而后会解决vue2
的兼容。这里咱们临时不深究vue2
的兼容解决。
if (__COMPAT__) { installAppCompatProperties(app, context, render)}
最初返回app
。至此createaApp
执行结束。
app利用实例
接下来咱们看下app
实例的结构:
const app: App = (context.app = { _uid: uid++, _component: rootComponent as ConcreteComponent, _props: rootProps, _container: null, _context: context, _instance: null, version, get config() { // ... }, set config(v) { // ... }, use(plugin: Plugin, ...options: any[]) { //... }, mixin(mixin: ComponentOptions) { //... }, component(name: string, component?: Component): any { //... }, directive(name: string, directive?: Directive) { //... }, mount( rootContainer: HostElement, isHydrate?: boolean, isSVG?: boolean ): any { //... }, unmount() { //... }, provide(key, value) { //... }})
_uid
:app
的惟一标识,每次都会应用uid
为新app
的惟一标识,在赋值后,uid
会进行自增,以便下一个app
应用_component
:根组件_props
:根组件所需的props
_container
:须要将根组件渲染到的容器_context
:app
的上下文_instance
:根组件的实例version
:vue
的版本get config
:获取上下文中的config
get config() { return context.config}
set config
:拦挡app.config
的set
操作,避免app.config
被批改set config(v) { if (__DEV__) { warn( `app.config cannot be replaced. Modify individual options instead.` ) }}
app.use()
应用app.use
办法装置plugin
。对于反复装置屡次的plugin
,只会进行装置一次,这都依附installedPlugins
,每次装置新的plugin
后,都会将plugin
存入installedPlugins
,这样如果再次装置同样的plugin
,就会防止屡次装置。
use(plugin: Plugin, ...options: any[]) { // 如果曾经装置过plugin,则不须要再次装置 if (installedPlugins.has(plugin)) { __DEV__ && warn(`Plugin has already been applied to target app.`) } else if (plugin && isFunction(plugin.install)) { // 如果存在plugin,并且plugin.install是个办法 // 将plugin增加到installedPlugins installedPlugins.add(plugin) // 调用plugin.install plugin.install(app, ...options) } else if (isFunction(plugin)) { 如果plugin是办法 // 将plugin增加到installedPlugins installedPlugins.add(plugin) // 调plugin plugin(app, ...options) } else if (__DEV__) { warn( `A plugin must either be a function or an object with an "install" ` + `function.` ) } // 最初返回app,以便能够链式调用app的办法 return app}
app.mixin()
应用app.mixin
进行全局混入,被混入的对象会被存在上下文中的mixins
中。留神mixin
只会在反对options api
的版本中能力应用,在mixin
中会通过__FEATURE_OPTIONS_API__
进行判断,这个变量会在打包过程中借助@rollup/plugin-replace
进行替换。
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}
app.component()
应用app.compoent
全局注册组件,也可用来获取name
对应的组件。被注册的组件会被存在上下文中的components
中。
component(name: string, component?: Component): any { // 验证组件名是否符合要求 if (__DEV__) { validateComponentName(name, context.config) } // 如果不存在component,那么会返回name对应的组件 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}
app.directive()
注册全局指令,也可用来获取name
对应的指令对象。注册的全局指令会被存入上下文中的directives
中。
directive(name: string, directive?: Directive) { // 验证指令名称 if (__DEV__) { validateDirectiveName(name) } // 如果不存在directive,则返回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}
app.mount()
此处的app.mount
并不是咱们平时应用到的mount
。创立完渲染器,执行完渲染器的createApp
后,会重写mount
办法,咱们应用的mount
办法是被重写的mount
办法。
mount( rootContainer: HostElement, isHydrate?: boolean, isSVG?: boolean): any { // 如果未挂载,开始挂载 if (!isMounted) { // 如果存在rootContainer.__vue_app__,阐明容器中曾经存在一个app实例了,须要先应用unmount进行卸载 if (__DEV__ && (rootContainer as any).__vue_app__) { warn( `There is already an app instance mounted on the host container.\n` + ` If you want to mount another app on the same host container,` + ` you need to unmount the previous app by calling \`app.unmount()\` first.` ) } // 创立根组件的虚构DOM const vnode = createVNode( rootComponent as ConcreteComponent, rootProps ) // 将上下文增加到根组件虚构dom的appContext属性中 vnode.appContext = context // HMR root reload if (__DEV__) { context.reload = () => { render(cloneVNode(vnode), rootContainer, isSVG) } } // 同构渲染 if (isHydrate && hydrate) { hydrate(vnode as VNode<Node, Element>, rootContainer as any) } else { // 客户端渲染 render(vnode, rootContainer, isSVG) } // 渲染实现后将isMounted置为true isMounted = true // 将容器增加到app的_container属性中 app._container = rootContainer // 将rootContainer.__vue_app__指向app实例 ;(rootContainer as any).__vue_app__ = app if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { // 将根组件实例赋给app._instance app._instance = vnode.component devtoolsInitApp(app, version) } // 返回根组件expose的属性 return getExposeProxy(vnode.component!) || vnode.component!.proxy } else if (__DEV__) { // 曾经挂载了 warn( `App has already been mounted.\n` + `If you want to remount the same app, move your app creation logic ` + `into a factory function and create fresh app instances for each ` + `mount - e.g. \`const createMyApp = () => createApp(App)\`` ) }}
app.unmount()
卸载利用实例。
unmount() { // 如果曾经挂载能力进行卸载 if (isMounted) { // 调用redner函数,此时虚构节点为null,代表会清空容器中的内容 render(null, app._container) if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { // 将app._instance置空 app._instance = null devtoolsUnmountApp(app) } // 删除容器中的__vue_app__ delete app._container.__vue_app__ } else if (__DEV__) { warn(`Cannot unmount an app that is not mounted.`) }}
app.provide()
全局注入一些数据。这些数据会被存入上下文对象的provides
中。
provide(key, value) { if (__DEV__ && (key as string | symbol) in context.provides) { warn( `App already provides property with key "${String(key)}". ` + `It will be overwritten with the new value.` ) } context.provides[key as string | symbol] = value return app}
总结
vue3
中的渲染器次要作用就是将虚构DOM转为实在DOM渲染到对应平台中,在这个渲染过程中会包含DOM的挂载、DOM的更新等操作。
通过baseCreateRenderer
办法会创立一个渲染器renderer
,renderer
中有三个办法:render
、hydrate
、createApp
,其中render
办法用来进行客户端渲染,hydrate
用来进行同构渲染,createApp
用来创立app
实例。baseCreateRenderer
中蕴含了大量的函数用来解决挂载组件、更新组件等操作。