眾所周知,在組件式開發中,最大的痛點就在于組件之間的通信,在 Vue 中,Vue 提供了各種各樣的組件通信方式,從基礎的 props/$emit 到用于兄弟組件通信的 EventBus,再到用于全域資料管理的 Vuex,
在這么多的組件通信方式中,provide/inject 顯得十分阿卡林(毫無存在感),但是,其實 provide/inject 也有它們的用武之地,今天,我們就來聊聊 Vue 中 provide/inject 的應用,
何為 provide/inject
provide/inject 是 Vue 在 2.2.0 版本新增的 API,官網介紹如下:
這對選項需要一起使用,以允許一個祖先組件向其所有子孫后代注入一個依賴,不論組件層次有多深,并在起上下游關系成立的時間里始終生效,如果你熟悉 React,這與 React 的背景關系特性很相似,
官網的解釋很讓人疑惑,那我翻譯下這幾句話:
provide 可以在祖先組件中指定我們想要提供給后代組件的資料或方法,而在任何后代組件中,我們都可以使用 inject 來接收 provide 提供的資料或方法,
舉個官網的??:
// 父級組件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子組件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
可以看到,父組件提供的 foo 變數被子組件成功接收并使用,
了解了 provide/inject 是什么后,我們再來使用使用 provide/inject,
使用 provide/inject 做全域狀態管理
在日常開發中,我們經常會使用 Vuex 做狀態管理,但是,我個人一直不喜歡使用 Vuex,原因在于 Vuex 為了保持狀態可被回溯追蹤,使用起來太過繁瑣;而我之前參與的專案,較少多人合作,這個功能對于我來說,意義不大,我僅僅只需要 Vuex 中提供全域狀態的功能,
那么,有沒有方便快捷的實作全域狀態的方法呢?當然有,這就是 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 有這么一個提示:
提示:
provide和inject系結并不是可回應的,這是刻意為之的,然而,如果你傳入了一個可監聽的物件,那么其物件的屬性還是可回應的,
也就是說,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 做全域狀態管理的原則:
- 多人協作時,做好作用域隔離
- 盡量使用一次性資料作為全域狀態
看起來,使用 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,也許你能發現一些不一般的風景,令你在解決一些問題時,事半功倍,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/175412.html
標籤:JavaScript
上一篇:前端獲取時間的方法
下一篇:js基礎閉包練習題
