01.基本概念
返回一个提供应用上下文的应用实例。应用实例挂载的整个组件树共享同一个上下文。
// packages/runtime-dom/src/index.ts
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
if (!isFunction(component) && !component.render && !component.template) {
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
}) as CreateAppFunction<Element>
// packages/runtime-dom/src/index.ts
function ensureRenderer() {
return (
renderer ||
(renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
)
}
// packages/runtime-core/src/renderer.ts
export interface Renderer<HostElement = RendererElement> {
render: RootRenderFunction<HostElement>
createApp: CreateAppFunction<HostElement>
}
// createRenderer 返回一个对象,具有 render 和 createApp 两个属性,
// render 是下面 mount 提到的渲染函数,这里不展开讲。createApp 方法是 createAppAPI 的返回值
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
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() {
},
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) {
}
})
return app
}
}
在 const proxy = mount(container, false, container instanceof SVGElement) 这句代码中打一个断点并且单步进入,可以得到 mount 方法的代码实现:
// packages/runtime-core/src/apiCreateApp.ts
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
// 用一个变量来控制 mount 只执行一次
if (!isMounted) {
// 创建一个虚拟node节点
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
return vnode.component!.proxy
}
}
可以看到,当我们单步跳过 render(vnode, rootContainer, isSvg) 这行代码时,hello world 字符串显示在浏览器上了,也就是说,mount 方法将 Vue 组件挂载到浏览器上,而 render 则是关键的渲染方法。
02. creatApp VS New VUE
我们知道在vue的之前的版本中;我们挂载app的方式多数通过new Vue的形式来创建;可是在Vue3中我同样可以通过creatApp这种模式来创建,他们都能够将App挂载到对应的html的dom节点;
import App from './App.vue'
// 通过 New的形式来搭建的app
new Vue({
render: (h) => h(App)
}).$mount('#app')
// creatApp
const app = createApp(App)
app.$mount('#app')
其实在单页面的应用中两者并没有很大的差别,但是我们需要开发一个比较大的vue应用,团队A需要一个vueA实例对象,它拥有全局组件A1,团队B需要一个vueB实例对象,它不需要全局组件A1,要求俩个vue实例的功能要完全独立,相互隔离,该如何实现?
事实上通过 New Vue 的方式实际上是通过实例化vue实现的;这种类的继承属于原形链的继承方式,他会见注册的component注册到对应的Vue的类属性上,至于细节过程可以通过源码理解到;这里面就不细说了,所以这里面为什么会有creatApp也就也就比较明显了吧;其实他也就是替代了对应的component的局部注册的模式;换句话说creatApp的模式防止了简化了对应的单应用的模式。以前的Vue并没有考虑到对应的微服务的这种形式的应用;通过这种模式就会变得更加的宽广了。