01.基本概念
返回一个提供应用上下文的应用实例。应用实例挂载的整个组件树共享同一个上下文。
// packages/runtime-dom/src/index.tsexport const createApp = ((...args) => {const app = ensureRenderer().createApp(...args)const { mount } = appapp.mount = (containerOrSelector: Element | ShadowRoot | string): any => {const container = normalizeContainer(containerOrSelector)if (!container) returnconst component = app._componentif (!isFunction(component) && !component.render && !component.template) {component.template = container.innerHTML}// clear content before mountingcontainer.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.tsfunction ensureRenderer() {return (renderer ||(renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions)))}// packages/runtime-core/src/renderer.tsexport 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 = falseconst 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.tsmount(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 reloadif (__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 = trueapp._container = rootContainer// for devtools and telemetry;(rootContainer as any).__vue_app__ = appreturn 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的形式来搭建的appnew Vue({render: (h) => h(App)}).$mount('#app')// creatAppconst app = createApp(App)app.$mount('#app')
其实在单页面的应用中两者并没有很大的差别,但是我们需要开发一个比较大的vue应用,团队A需要一个vueA实例对象,它拥有全局组件A1,团队B需要一个vueB实例对象,它不需要全局组件A1,要求俩个vue实例的功能要完全独立,相互隔离,该如何实现?
事实上通过 New Vue 的方式实际上是通过实例化vue实现的;这种类的继承属于原形链的继承方式,他会见注册的component注册到对应的Vue的类属性上,至于细节过程可以通过源码理解到;这里面就不细说了,所以这里面为什么会有creatApp也就也就比较明显了吧;其实他也就是替代了对应的component的局部注册的模式;换句话说creatApp的模式防止了简化了对应的单应用的模式。以前的Vue并没有考虑到对应的微服务的这种形式的应用;通过这种模式就会变得更加的宽广了。
