Vue中元件間通訊的方式
阿新 • • 發佈:2021-01-28
# Vue中元件間通訊的方式
`Vue`中元件間通訊包括父子元件、兄弟元件、隔代元件之間通訊。
## props $emit
這種元件通訊的方式是我們運用的非常多的一種,`props`以單向資料流的形式可以很好的完成父子元件的通訊,所謂單向資料流,就是資料只能通過`props`由父元件流向子元件,而子元件並不能通過修改`props`傳過來的資料修改父元件的相應狀態,所有的`prop`都使得其父子`prop`之間形成了一個單向下行繫結,父級`prop`的更新會向下流動到子元件中,但是反過來則不行,這樣會防止從子元件意外改變父級元件的狀態,導致難以理解資料的流向而提高了專案維護難度。實際上如果傳入一個基本資料型別給子元件,在子元件中修改這個值的話`Vue`中會出現警告,如果對於子元件傳入一個引用型別的物件的話,在子元件中修改是不會出現任何提示的,這兩種情況都屬於改變了父子元件的單向資料流,是不符合可維護的設計方式的。
正因為這個特性,而我們會有需要更改父元件值的需求,就有了對應的`$emit`,當我們在元件上定義了自定義事件,事件就可以由`vm.$emit`觸發,回撥函式會接收所有傳入事件觸發函式的額外引數,`$emit`實際上就是是用來觸發當前例項上的事件,對此我們可以在父元件自定義一個處理接受變化狀態的邏輯,然後在子元件中如若相關的狀態改變時,就觸發父元件的邏輯處理事件。
### 父元件向子元件傳值
父元件向子元件傳值通過`prop`傳遞值即可。
```html
我是子元件,接收:{{ msg }}
```
```html
```
### 子元件向父元件傳值
子元件向父元件傳值需要通過事件的觸發,將更改值的行為傳遞到父元件去執行。
```html
我是子元件,接收:{{ msg }}
```
```html
```
### v-model
`v-model`通常稱為資料雙向繫結,也可以稱得上是一種父子元件間傳值的方式,是當前元件與`input`等元件進行父子傳值,其本質上就是一種語法糖,通過`props`以及`input`(預設情況下)的事件的`event`中攜帶的值完成,我們可以自行實現一個`v-model`。
```html
{{msg}}
```
### sync修飾符
`sync`修飾符也可以稱為一個語法糖,在`Vue 2.3`之後新的`.sync`修飾符所實現的已經不再像`Vue 1.0`那樣是真正的雙向繫結,而是和`v-model`類似,是一種語法糖的形式,也可以稱為一種縮寫的形式,在下面父元件兩種寫法是完全等同的。
```html
我是子元件,接收:{{ msg }}
```
```html
```
## provide inject
類似於`React`的`Context API`,在父元件中通過`provider`來提供屬性,然後在子元件中通過`inject`來注入變數,不論子元件有多深,只要呼叫了`inject`那麼就可以注入在`provider`中提供的資料,而不是侷限於只能從當前父元件的`prop`屬性來獲取資料,只要在父元件內的資料,子元件都可以呼叫。當然`Vue`中註明了`provide`和`inject`主要在開發高階外掛`/`元件庫時使用,並不推薦用於普通應用程式程式碼中。
```html
inject: {{msg}}
```
```html
```
## $attrs $listeners
這種元件通訊的方式適合直接的父子元件,假設此時我們有三個元件分別為`A`、`B`、`C`,父元件`A`下面有子元件`B`,父元件`B`下面有子元件`C`,這時如果元件`A`直接想傳遞資料給元件`C`那就不能直接傳遞了,只能是元件`A`通過`props`將資料傳給元件`B`,然後元件`B`獲取到元件`A`傳遞過來的資料後再通過`props`將資料傳給元件`C`,當然這種方式是非常複雜的,無關元件中的邏輯業務增多了,程式碼維護也沒變得困難,再加上如果巢狀的層級越多邏輯也複雜,無關程式碼越多,針對這樣一個問題,`Vue 2.4`提供了`$attrs`和`$listeners`來實現能夠直接讓元件`A`直接傳遞訊息給元件`C`。
```html
```
```html
```
```html
```
## $children $parent
這種方式就比較直觀了,直接操作父子元件的例項,`$parent`就是父元件的例項物件,而`$children`就是當前例項的直接子元件例項陣列了,官方文件的說明是子例項可以用`this.$parent`訪問父例項,子例項被推入父例項的`$children`陣列中,節制地使用`$parent`和`$children`它們的主要目的是作為訪問元件的應急方法,更推薦用`props`和`events`實現父子元件通訊。此外在`Vue2`之後移除的`$dispatch`和`$broadcast`也可以通過`$children`與`$parent`進行實現,當然不推薦這樣做,官方推薦的方式還是更多簡明清晰的元件間通訊和更好的狀態管理方案如`Vuex`,實際上很多開源框架都還是自己實現了這種元件通訊的方式,例如`Mint UI`、`Element UI`和`iView`等。
```html
```
```html
```
## EventBus
在專案規模不大的情況下,完全可以使用中央事件匯流排`EventBus` 的方式,`EventBus`可以比較完美地解決包括父子元件、兄弟元件、隔代元件之間通訊,實際上就是一個觀察者模式,觀察者模式建立了一種物件與物件之間的依賴關係,一個物件發生改變時將自動通知其他物件,其他物件將相應做出反應。所以發生改變的物件稱為觀察目標,而被通知的物件稱為觀察者,一個觀察目標可以對應多個觀察者,而且這些觀察者之間沒有相互聯絡,可以根據需要增加和刪除觀察者,使得系統更易於擴充套件。首先我們需要實現一個訂閱釋出類,並作為全域性物件掛載到`Vue.prototype`,作為`Vue`例項中可呼叫的全域性物件使用,此外務必注意在元件銷燬的時候解除安裝訂閱的事件呼叫,否則會造成記憶體洩漏。
```javascript
// 實現一個PubSub模組
var PubSub = function() {
this.handlers = {};
}
PubSub.prototype = {
on: function(key, handler) { // 訂閱
if (!(key in this.handlers)) this.handlers[key] = [];
this.handlers[key].push(handler);
},
off: function(key, handler) { // 解除安裝
const index = this.handlers[key].findIndex(item => item === handler);
if (index < 0) return false;
if (this.handlers[key].length === 1) delete this.handlers[key];
else this.handlers[key].splice(index, 1);
return true;
},
commit: function(key, ...args) { // 觸發
if (!this.handlers[key]) return false;
this.handlers[key].forEach(handler => handler.apply(this, args));
return true;
},
}
export { PubSub }
export default { PubSub }
```
```html
{{msg}}
```
```html
{{msg}}
```
## Vuex
`Vuex`是一個專為`Vue.js`應用程式開發的狀態管理模式,其採用集中式儲存管理應用的所有元件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
每一個`Vuex`應用的核心就是`store`倉庫,`store`基本上就是一個容器,它包含著你的應用中大部分的狀態`state`。`Vuex`和單純的全域性物件有以下兩點不同:
* `Vuex`的狀態儲存是響應式的,當`Vue`元件從`store`中讀取狀態的時候,若`store`中的狀態發生變化,那麼相應的元件也會相應地得到高效更新。
* 不能直接改變`store`中的狀態,改變`store`中的狀態的唯一途徑就是顯式地提交`mutation`,這樣使得我們可以方便地跟蹤每一個狀態的變化。
實際上我們可以得到更多使用`Vuex`的優點:
* 可以使用時間旅行功能。
* `Vuex`專做態管理,由一個統一的方法去修改資料,全部的修改都是可以追溯的。
* 在做日誌蒐集,埋點的時候,有`Vuex`更方便。
* `Vuex`不會造成全域性變數的汙染,同時解決了父元件與孫元件,以及兄弟元件之間通訊的問題。
當然如果專案足夠小,使用`Vuex`可能是繁瑣冗餘的。如果應用夠簡單,最好不要使用`Vuex`,上文中的一個簡單的`store`模式就足夠了。
在下面例子中,我們通過提交`mutation`的方式,而非直接改變`store.state.count`,是因為我們想要更明確地追蹤到狀態的變化。這個簡單的約定能夠讓你的意圖更加明顯,這樣你在閱讀程式碼的時候能更容易地解讀應用內部的狀態改變。此外這樣也讓我們有機會去實現一些能記錄每次狀態改變,儲存狀態快照的除錯工具。
```javascript
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment: function(state) {
state.count++;
}
}
})
store.commit("increment");
console.log(store.state.count); // 1
```
由於`store`中的狀態是響應式的,在元件中呼叫`store`中的狀態簡單到僅需要在計算屬性中返回即可。觸發變化也僅僅是在元件的`methods`中提交`mutation`即可。
```javascript
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment: state => state.count++,
decrement: state => state.count--
}
})
new Vue({
el: "#app",
store,
computed: {
count: function() {
return this.$store.state.count;
}
},
methods: {
increment: function() {
this.$store.commit("increment");
},
decrement: function() {
this.$store.commit("decrement");
}
}
})
```
## 每日一題
```
https://github.com/WindrunnerMax/EveryDay
```
## 參考
```
https://zhuanlan.zhihu.com/p/109700915
https://juejin.cn/post/6844903887162310669
https://juejin.cn/post/6844903784963899405
https://segmentfault.com/a/1190000022083517
https://github.com/yangtao2o/learn/issues/97
https://github.com/YangYmimi/read-vue/issues