聊聊 Vue 中 provide/inject 的應用

衆所周知,在組件式開發中,最大的痛點就在於組件之間的通訊。在 Vue 中,Vue 提供了各類各樣的組件通訊方式,從基礎的 props/$emit 到用於兄弟組件通訊的 EventBus,再到用於全局數據管理的 Vuex。vue

在這麼多的組件通訊方式中,provide/inject 顯得十分阿卡林(毫無存在感)。可是,其實 provide/inject 也有它們的用武之地。今天,咱們就來聊聊 Vue 中 provide/inject 的應用。app

何爲 provide/inject

provide/inject 是 Vue 在 2.2.0 版本新增的 API,官網介紹以下:ide

這對選項須要一塊兒使用,以容許一個祖先組件向其全部子孫後代注入一個依賴,不論組件層次有多深,並在起上下游關係成立的時間裏始終生效。若是你熟悉 React,這與 React 的上下文特性很類似。學習

官網的解釋很讓人疑惑,那我翻譯下這幾句話:this

provide 能夠在祖先組件中指定咱們想要提供給後代組件的數據或方法,而在任何後代組件中,咱們均可以使用 inject 來接收 provide 提供的數據或方法。spa

舉個官網的🌰:翻譯

// 父級組件提供 'foo'
var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}

// 子組件注入 'foo'
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}
複製代碼

能夠看到,父組件提供的 foo 變量被子組件成功接收並使用。設計

瞭解了 provide/inject 是什麼後,咱們再來使用使用 provide/inject。code

使用 provide/inject 作全局狀態管理

在平常開發中,咱們常常會使用 Vuex 作狀態管理,可是,我我的一直不喜歡使用 Vuex,緣由在於 Vuex 爲了保持狀態可被回溯追蹤,使用起來太過繁瑣;而我以前參與的項目,較少多人合做,這個功能對於我來講,意義不大,我僅僅只須要 Vuex 中提供全局狀態的功能。orm

那麼,有沒有方便快捷的實現全局狀態的方法呢?固然有,這就是 provide/inject 這個黑科技 API 的一種使用方法。

不少人也許會想到一種方式:在根組件中,傳入變量,而後在後代組件中使用便可。

// 根組件提供一個非響應式變量給後代組件
export default {
  provide () {
    return {
      text: 'bar'
    }
  }
}

// 後代組件注入 'app'
<template>
	<div>{{this.text}}</div>
</template>
<script>
  export default {
    inject: ['text'],
    created() {
      this.text = 'baz' // 在模板中,依然顯示 'bar'
    }
  }
</script>
複製代碼

這個想法,說對也對,說不對也不對,緣由在於 provide 的特殊性。

在官網文檔中關於 provide/inject 有這麼一個提示:

提示:provideinject 綁定並非可響應的。這是刻意爲之的。然而,若是你傳入了一個可監聽的對象,那麼其對象的屬性仍是可響應的。

也就是說,Vue 不會對 provide 中的變量進行響應式處理。因此,要想 inject 接受的變量是響應式的,provide 提供的變量自己就須要是響應式的。

因爲組件內部的各類狀態就是可響應的,因此咱們直接在根組件中將組件自己注入 provide,此時,咱們能夠在後代組件中任意訪問根組件中的全部狀態,根組件就成爲了全局狀態的容器,仔細想一想,是否是很像 React 中的 context 呢?

代碼以下:

// 根組件提供將自身提供給後代組件
export default {
  provide () {
    return {
      app: this
    }
  },
  data () {
    return {
      text: 'bar'
    }
  }
}

// 後代組件注入 'app'
<template>
	<div>{{this.app.text}}</div>
</template>
<script>
  export default {
    inject: ['app'],
    created() {
      this.app.text = 'baz' // 在模板中,顯示 'baz'
    }
  }
</script>	
複製代碼

也許有的同窗會問:使用 $root 依然可以取到根節點,那麼咱們何須使用 provide/inject 呢?

在實際開發中,一個項目經常有多人開發,每一個人有可能須要不一樣的全局變量,若是全部人的全局變量都統必定義在根組件,勢必會引發變量衝突等問題。

使用 provide/inject 不一樣模塊的入口組件傳給各自的後代組件能夠完美的解決該問題。

慎用 provide/inject

既然 provide/inject 如此好用,那麼,爲何 Vue 官方還要推薦咱們使用 Vuex,而不是用原生的 API 呢?

我在前面提到過,Vuex 和 provide/inject 最大的區別在於,Vuex 中的全局狀態的每次修改是能夠追蹤回溯的,而 provide/inject 中變量的修改是沒法控制的,換句話說,你不知道是哪一個組件修改了這個全局狀態。

Vue 的設計理念借鑑了 React 中的單向數據流原則(雖然有 sync 這種破壞單向數據流的傢伙),而 provide/inject 明顯破壞了單向數據流原則。試想,若是有多個後代組件同時依賴於一個祖先組件提供的狀態,那麼只要有一個組件修改了該狀態,那麼全部組件都會受到影響。這一方面增長了耦合度,另外一方面,使得數據變化不可控。若是在多人協做開發中,這將成爲一個噩夢。

在這裏,我總結了兩條條使用 provide/inject 作全局狀態管理的原則:

  1. 多人協做時,作好做用域隔離
  2. 儘可能使用一次性數據做爲全局狀態

看起來,使用 provide/inject 作全局狀態管理好像很危險,那麼有沒有 provide/inject 更好的使用方式呢?固然有,那就是使用 provide/inject 編寫組件。

使用 provide/inject 編寫組件

使用 provide/inject 作組件開發,是 Vue 官方文檔中提倡的一種作法。

以我比較熟悉的 elementUI 來舉例:

在 elementUI 中有 Button(按鈕)組件,當在 Form(表單)組件中使用時,它的尺寸會同時受到外層的 FormItem 組件以及更外層的 Form 組件中的 size 屬性的影響。

若是是常規方案,咱們能夠經過 props 從 Form 開始,一層層往下傳遞屬性值。看起來只須要傳遞傳遞兩層便可,還能夠接受。可是,Form 的下一層組件不必定是 FormItem,FormItem 的下一層組件不必定是 Button,它們之間還能夠嵌套其餘組件,也就是說,層級關係不肯定。若是使用 props,咱們寫的組件會出現強耦合的狀況。

provide/inject 能夠完美的解決這個問題,只須要向後代注入組件自己(上下文),後代組件中能夠無視層級任意訪問祖先組件中的狀態。

部分源碼以下:

// Button 組件核心源碼
export default {
    name: 'ElButton',
    // 經過 inject 獲取 elForm 以及 elFormItem 這兩個組件
    inject: {
        elForm: {
            default: ''
        },
        elFormItem: {
            default: ''
        }
    },
    // ...
    computed: {
        _elFormItemSize() {
            return (this.elFormItem || {}).elFormItemSize;
        },
        buttonSize() {
            return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
        },
        //...
    },
    // ...
};
複製代碼

總結

其實在 Vue 的學習中,遵循着二八法則,咱們經常使用的 20% 的 API 就能解決大部分平常問題,剩餘的 API 感受用處不大。可是,抽點時間去了解那些冷門的 API,也許你能發現一些不通常的風景,令你在解決一些問題時,事半功倍。

相關文章
相關標籤/搜索