vue3-創(chuàng)建應(yīng)用createApp

先看一下vue-next官方文檔的介紹:

每個(gè) Vue 應(yīng)用都是通過用 createApp 函數(shù)創(chuàng)建一個(gè)新的應(yīng)用實(shí)例開始的

傳遞給 createApp 的選項(xiàng)用于配置根組件颇玷。當(dāng)我們掛載應(yīng)用時(shí)铐拐,該組件被用作渲染的起點(diǎn)件相。

一個(gè)應(yīng)用需要被掛載到一個(gè) DOM 元素中箱蝠。例如驰怎,如果我們想把一個(gè) Vue 應(yīng)用掛載到<div id="app"></div>吃粒,我們應(yīng)該傳遞 #app

我們將分為兩部分進(jìn)行渲染過程的理解:

  • 創(chuàng)建應(yīng)用實(shí)例巧娱,函數(shù)createApp的剖析
  • 應(yīng)用實(shí)例掛載闺金, 函數(shù)mount方法掛載過程

本篇詳細(xì)講述調(diào)用方法createApp過程

image.png

創(chuàng)建應(yīng)用實(shí)例 createApp

下面是一個(gè)簡單的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')

現(xiàn)在我們從createApp函數(shù)為入口逾滥,去了解應(yīng)用創(chuàng)建的過程。

查看官方文檔和上面的例子我們可以知道败匹,createApp方法接收的是根組件對象作為參數(shù)寨昙,并返回了一個(gè)有mount方法的應(yīng)用實(shí)例對象。

按照依賴關(guān)系可以找到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>

這里做了兩件事情:

  • 創(chuàng)建app應(yīng)用實(shí)例: ensureRenderer().createApp(...args)
  • 重寫了app.mount方法掀亩。document.querySelector方法獲取HTMLElement對象作為參數(shù)傳入原mount方法舔哪。該部分會在mount段落詳細(xì)講解。
image.png

ensureRenderer

ensureRenderer函數(shù)的目的是惰性創(chuàng)建renderer對象槽棍,這樣做目的是在用戶只引入reactivity模塊時(shí)捉蚤,對renderer核心邏輯部分可以進(jìn)行tree-shake。

renderer對象包含三個(gè)屬性:
render方法刹泄、 hydrate( ssr客戶端激活相關(guān))外里、createApp方法

renderer對象實(shí)際上是方法createRenderer函數(shù)返回的。

// nodeOps: dom節(jié)點(diǎn)增刪改查操作的原生api
const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps)

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

createRenderer

這個(gè)方法在packages/runtime-core/src/renderer.ts中定義特石。

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

createRenderer方法接受兩個(gè)通用的類型參數(shù)HostNodeHostElement盅蝗。其目的是在自定義渲染器中可以傳入特定于平臺的類型;

例如:
對于瀏覽器環(huán)境runtime-dom姆蘸,HostNode將是DOM Node接口墩莫;HostElement將是DOM Element接口。

Element繼承了Node類逞敷,也就是說Element是Node多種類型中的一種狂秦,即當(dāng)NodeType為1時(shí)Node即為ElementNode,另外Element擴(kuò)展了Node推捐,Element擁有id裂问、class、children等屬性。

baseCreateRenderer

這個(gè)方法也在packages/runtime-core/src/renderer.ts中定義
該方法比較長堪簿,暫時(shí)忽略中間代碼痊乾。該函數(shù)執(zhí)行返回了一個(gè)對象(上文中的renderer對象):

function baseCreateRenderer(
  options: RendererOptions,
  createHydrationFns?: typeof createHydrationFunctions
): any {
  ...
  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }
}
  • render:接受兩個(gè)參數(shù)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客戶端激活相關(guān)
  • createApp:接收方法createAppAPI(render, hydrate)的返回值

createAppAPI

這個(gè)方法在packages/runtime-core/src/apiCreateApp.ts中定義哪审。

在這里createAppAPI的返回結(jié)果是一個(gè)函數(shù)createApp,這里終于找到了demo中調(diào)用的那個(gè)接受跟組件對象的createApp函數(shù)虑瀑。

createApp返回了應(yīng)用實(shí)例app對象湿滓,其中包含了我們比較熟悉的一些方法,例如:mixin舌狗、component叽奥、directive等;

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
  }
}

下面就是在應(yīng)用實(shí)例app還沒有調(diào)用mount方法進(jìn)行掛載前的屬性:

image.png

這里強(qiáng)調(diào)一下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')

從技術(shù)上講把夸,Vue 2沒有“應(yīng)用”的概念而线。我們定義為應(yīng)用的只是通過創(chuàng)建的根Vue實(shí)例new Vue()。從同一Vue構(gòu)造函數(shù)創(chuàng)建的每個(gè)根實(shí)例都共享相同的全局配置恋日。

Vue當(dāng)前的某些全局API和配置會永久更改全局狀態(tài)膀篮。這會導(dǎo)致一些問題:

  • 全局配置更容易使測試過程中意外污染其他測試案例
  • 影響每一個(gè)根實(shí)例
Vue.mixin({ /* ... */ })

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

vue3 應(yīng)用程序?qū)嵗?/h3>

createApp返回一個(gè)提供應(yīng)用上下文的應(yīng)用實(shí)例。應(yīng)用實(shí)例掛載的整個(gè)組件樹共享同一個(gè)上下文岂膳。該上下文提供了先前在Vue 2.x中“全局”的配置誓竿。該實(shí)例不會被應(yīng)用于其他實(shí)例的任何全局配置所污染。共享實(shí)例屬性應(yīng)附加到應(yīng)用程序?qū)嵗?code>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')

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谈截,一起剝皮案震驚了整個(gè)濱河市筷屡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌簸喂,老刑警劉巖毙死,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異喻鳄,居然都是意外死亡扼倘,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門除呵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來再菊,“玉大人,你說我怎么就攤上這事颜曾【腊危” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵泛豪,是天一觀的道長稠诲。 經(jīng)常有香客問我侦鹏,道長,這世上最難降的妖魔是什么臀叙? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任种柑,我火速辦了婚禮,結(jié)果婚禮上匹耕,老公的妹妹穿的比我還像新娘。我一直安慰自己荠雕,他們只是感情好稳其,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著炸卑,像睡著了一般既鞠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盖文,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天嘱蛋,我揣著相機(jī)與錄音,去河邊找鬼五续。 笑死洒敏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的疙驾。 我是一名探鬼主播凶伙,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼它碎!你這毒婦竟也來了函荣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤扳肛,失蹤者是張志新(化名)和其女友劉穎傻挂,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挖息,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡金拒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了旋讹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片殖蚕。...
    茶點(diǎn)故事閱讀 39,739評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖沉迹,靈堂內(nèi)的尸體忽然破棺而出睦疫,到底是詐尸還是另有隱情,我是刑警寧澤鞭呕,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布蛤育,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏瓦糕。R本人自食惡果不足惜底洗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望咕娄。 院中可真熱鬧亥揖,春花似錦、人聲如沸圣勒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽圣贸。三九已至挚歧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吁峻,已是汗流浹背滑负。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留用含,地道東北人矮慕。 一個(gè)月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像啄骇,于是被迫代替她去往敵國和親凡傅。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容