01.基本概念

返回一个提供应用上下文的应用实例。应用实例挂载的整个组件树共享同一个上下文。

  1. // packages/runtime-dom/src/index.ts
  2. export const createApp = ((...args) => {
  3. const app = ensureRenderer().createApp(...args)
  4. const { mount } = app
  5. app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
  6. const container = normalizeContainer(containerOrSelector)
  7. if (!container) return
  8. const component = app._component
  9. if (!isFunction(component) && !component.render && !component.template) {
  10. component.template = container.innerHTML
  11. }
  12. // clear content before mounting
  13. container.innerHTML = ''
  14. const proxy = mount(container, false, container instanceof SVGElement)
  15. if (container instanceof Element) {
  16. container.removeAttribute('v-cloak')
  17. container.setAttribute('data-v-app', '')
  18. }
  19. return proxy
  20. }
  21. return app
  22. }) as CreateAppFunction<Element>
  23. // packages/runtime-dom/src/index.ts
  24. function ensureRenderer() {
  25. return (
  26. renderer ||
  27. (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
  28. )
  29. }
  30. // packages/runtime-core/src/renderer.ts
  31. export interface Renderer<HostElement = RendererElement> {
  32. render: RootRenderFunction<HostElement>
  33. createApp: CreateAppFunction<HostElement>
  34. }
  35. // createRenderer 返回一个对象,具有 render 和 createApp 两个属性,
  36. // render 是下面 mount 提到的渲染函数,这里不展开讲。createApp 方法是 createAppAPI 的返回值
  37. export function createAppAPI<HostElement>(
  38. render: RootRenderFunction,
  39. hydrate?: RootHydrateFunction
  40. ): CreateAppFunction<HostElement> {
  41. return function createApp(rootComponent, rootProps = null) {
  42. if (rootProps != null && !isObject(rootProps)) {
  43. __DEV__ && warn(`root props passed to app.mount() must be an object.`)
  44. rootProps = null
  45. }
  46. const context = createAppContext()
  47. const installedPlugins = new Set()
  48. let isMounted = false
  49. const app: App = (context.app = {
  50. _uid: uid++,
  51. _component: rootComponent as ConcreteComponent,
  52. _props: rootProps,
  53. _container: null,
  54. _context: context,
  55. _instance: null,
  56. version,
  57. get config() {
  58. },
  59. set config(v) {
  60. },
  61. use(plugin: Plugin, ...options: any[]) {
  62. },
  63. mixin(mixin: ComponentOptions) {
  64. },
  65. component(name: string, component?: Component): any {
  66. },
  67. directive(name: string, directive?: Directive) {
  68. },
  69. mount(
  70. rootContainer: HostElement,
  71. isHydrate?: boolean,
  72. isSVG?: boolean
  73. ): any {
  74. },
  75. unmount() {
  76. },
  77. provide(key, value) {
  78. }
  79. })
  80. return app
  81. }
  82. }

在 const proxy = mount(container, false, container instanceof SVGElement) 这句代码中打一个断点并且单步进入,可以得到 mount 方法的代码实现:

  1. // packages/runtime-core/src/apiCreateApp.ts
  2. mount(
  3. rootContainer: HostElement,
  4. isHydrate?: boolean,
  5. isSVG?: boolean
  6. ): any {
  7. // 用一个变量来控制 mount 只执行一次
  8. if (!isMounted) {
  9. // 创建一个虚拟node节点
  10. const vnode = createVNode(
  11. rootComponent as ConcreteComponent,
  12. rootProps
  13. )
  14. // store app context on the root VNode.
  15. // this will be set on the root instance on initial mount.
  16. vnode.appContext = context
  17. // HMR root reload
  18. if (__DEV__) {
  19. context.reload = () => {
  20. render(cloneVNode(vnode), rootContainer, isSVG)
  21. }
  22. }
  23. if (isHydrate && hydrate) {
  24. hydrate(vnode as VNode<Node, Element>, rootContainer as any)
  25. } else {
  26. render(vnode, rootContainer, isSVG)
  27. }
  28. isMounted = true
  29. app._container = rootContainer
  30. // for devtools and telemetry
  31. ;(rootContainer as any).__vue_app__ = app
  32. return vnode.component!.proxy
  33. }
  34. }

可以看到,当我们单步跳过 render(vnode, rootContainer, isSvg) 这行代码时,hello world 字符串显示在浏览器上了,也就是说,mount 方法将 Vue 组件挂载到浏览器上,而 render 则是关键的渲染方法。

02. creatApp VS New VUE

我们知道在vue的之前的版本中;我们挂载app的方式多数通过new Vue的形式来创建;可是在Vue3中我同样可以通过creatApp这种模式来创建,他们都能够将App挂载到对应的html的dom节点;

  1. import App from './App.vue'
  2. // 通过 New的形式来搭建的app
  3. new Vue({
  4. render: (h) => h(App)
  5. }).$mount('#app')
  6. // creatApp
  7. const app = createApp(App)
  8. app.$mount('#app')

其实在单页面的应用中两者并没有很大的差别,但是我们需要开发一个比较大的vue应用,团队A需要一个vueA实例对象,它拥有全局组件A1,团队B需要一个vueB实例对象,它不需要全局组件A1,要求俩个vue实例的功能要完全独立,相互隔离,该如何实现?

事实上通过 New Vue 的方式实际上是通过实例化vue实现的;这种类的继承属于原形链的继承方式,他会见注册的component注册到对应的Vue的类属性上,至于细节过程可以通过源码理解到;这里面就不细说了,所以这里面为什么会有creatApp也就也就比较明显了吧;其实他也就是替代了对应的component的局部注册的模式;换句话说creatApp的模式防止了简化了对应的单应用的模式。以前的Vue并没有考虑到对应的微服务的这种形式的应用;通过这种模式就会变得更加的宽广了。