vue3-创建应用createApp

先看一下vue-next官方文档的介绍:

每个 Vue 应用都是通过用 createApp 函数创建一个新的应用实例开始的

传递给 createApp 的选项用于配置根组件。当我们挂载应用时,该组件被用作渲染的起点。

一个应用需要被挂载到一个 DOM 元素中。例如,如果我们想把一个 Vue 应用挂载到<div id="app"></div>,我们应该传递 #app

我们将分为两部分进行渲染过程的理解:

  • 创建应用实例,函数createApp的剖析
  • 应用实例挂载, 函数mount方法挂载过程

本篇详细讲述调用方法createApp过程

image.png

创建应用实例 createApp

下面是一个简单的demo

<!-- template -->
  <div id="app">
    <input v-model="value"/>
    <p>双向绑定:{{value}}</p>
    <hello-comp person-name="zhangsan"/>
  </div>
const { createApp } = Vue
const helloComp = {
      name: 'hello-comp',
      props: {
        personName: {
          type: String,
          default: 'wangcong'
        }
      },
      template: '<p>hello {{personName}}!</p>'
    }
const app = {
  data() {
    return {
      value: '',
      info: {
        name: 'tom',
        age: 18
      }
    }
  },
  components: {
    'hello-comp': helloComp
  },
  mounted() {
    console.log(this.value, this.info)
  },
}
createApp(app).mount('#app')

现在我们从createApp函数为入口,去了解应用创建的过程。

查看官方文档和上面的例子我们可以知道,createApp方法接收的是根组件对象作为参数,并返回了一个有mount方法的应用实例对象。

按照依赖关系可以找到createApp方法出自packages/runtime-dom/src/index.ts

export const createApp = ((...args) => {
  const app = ensureRenderer().createApp(...args)

  if (__DEV__) {
    injectNativeTagCheck(app)
  }

  const { mount } = app
  app.mount = (containerOrSelector: Element | 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)
    container.removeAttribute('v-cloak')
    container.setAttribute('data-v-app', '')
    return proxy
  }

  return app
}) as CreateAppFunction<Element>

这里做了两件事情:

  • 创建app应用实例: ensureRenderer().createApp(...args)
  • 重写了app.mount方法。document.querySelector方法获取HTMLElement对象作为参数传入原mount方法。该部分会在mount段落详细讲解。
image.png

ensureRenderer

ensureRenderer函数的目的是惰性创建renderer对象,这样做目的是在用户只引入reactivity模块时,对renderer核心逻辑部分可以进行tree-shake。

renderer对象包含三个属性:
render方法、 hydrate( ssr客户端激活相关)、createApp方法

renderer对象实际上是方法createRenderer函数返回的。

// nodeOps: dom节点增删改查操作的原生api
const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps)

let renderer: Renderer<Element> | HydrationRenderer
function ensureRenderer() {
  return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}

createRenderer

这个方法在packages/runtime-core/src/renderer.ts中定义。

export function createRenderer<
  HostNode = RendererNode,
  HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
  return baseCreateRenderer<HostNode, HostElement>(options)
}

createRenderer方法接受两个通用的类型参数HostNodeHostElement。其目的是在自定义渲染器中可以传入特定于平台的类型;

例如:
对于浏览器环境runtime-domHostNode将是DOM Node接口;HostElement将是DOM Element接口。

Element继承了Node类,也就是说Element是Node多种类型中的一种,即当NodeType为1时Node即为ElementNode,另外Element扩展了Node,Element拥有id、class、children等属性。

baseCreateRenderer

这个方法也在packages/runtime-core/src/renderer.ts中定义
该方法比较长,暂时忽略中间代码。该函数执行返回了一个对象(上文中的renderer对象):

function baseCreateRenderer(
  options: RendererOptions,
  createHydrationFns?: typeof createHydrationFunctions
): any {
  ...
  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }
}
  • render:接受两个参数VNodeElement
const render: RootRenderFunction = (vnode, container) => {
    if (vnode == null) {
      if (container._vnode) {
        unmount(container._vnode, null, null, true)
      }
    } else {
      patch(container._vnode || null, vnode, container)
    }
    flushPostFlushCbs()
    container._vnode = vnode
  }
  • hydrate:与ssr客户端激活相关
  • createApp:接收方法createAppAPI(render, hydrate)的返回值

createAppAPI

这个方法在packages/runtime-core/src/apiCreateApp.ts中定义。

在这里createAppAPI的返回结果是一个函数createApp,这里终于找到了demo中调用的那个接受跟组件对象的createApp函数。

createApp返回了应用实例app对象,其中包含了我们比较熟悉的一些方法,例如:mixincomponentdirective等;

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,

      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[]) {
        ...
        return app
      },

      mixin(mixin: ComponentOptions) {
        ...
        return app
      },

      // 组件注册
      component(name: string, component?: Component): any {
        ...
        return app
      },

      // 指令注册
      directive(name: string, directive?: Directive) {
        ...
        return app
      },

      // dom挂载
      mount(rootContainer: HostElement, isHydrate?: boolean): any {
        ...
        return app
      },

      // 卸载
      unmount() {
        if (isMounted) {
          render(null, app._container)
          if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
            devtoolsUnmountApp(app)
          }
        } else if (__DEV__) {
          warn(`Cannot unmount an app that is not mounted.`)
        }
      },

      // 注入
      provide(key, value) {
        ...
        return app
      }
    }

    return app
  }
}

下面就是在应用实例app还没有调用mount方法进行挂载前的属性:

image.png

这里强调一下app._component引用的就是我们传入createApp的根组件对象

对比

2.x global API:

import Vue from 'vue'
import App from './App.vue'

Vue.config.ignoredElements = [/^app-/]
Vue.use(/* ... */)
Vue.mixin(/* ... */)
Vue.component(/* ... */)
Vue.directive(/* ... */)

Vue.prototype.customProperty = () => {}

new Vue({
  render: h => h(App)
}).$mount('#app')

从技术上讲,Vue 2没有“应用”的概念。我们定义为应用的只是通过创建的根Vue实例new Vue()。从同一Vue构造函数创建的每个根实例都共享相同的全局配置。

Vue当前的某些全局API和配置会永久更改全局状态。这会导致一些问题:

  • 全局配置更容易使测试过程中意外污染其他测试案例
  • 影响每一个根实例
Vue.mixin({ /* ... */ })

const app1 = new Vue({ el: '#app-1' })
const app2 = new Vue({ el: '#app-2' })

vue3 应用程序实例

createApp返回一个提供应用上下文的应用实例。应用实例挂载的整个组件树共享同一个上下文。该上下文提供了先前在Vue 2.x中“全局”的配置。该实例不会被应用于其他实例的任何全局配置所污染。共享实例属性应附加到应用程序实例的config.globalProperties

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

app.config.isCustomElement = tag => tag.startsWith('app-')
app.use(/* ... */)
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)
app.provide(/* ... */)

app.config.globalProperties.customProperty = () => {}

app.mount(App, '#app')

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容