我們可以總結下組件通信的幾大方法:
一、props屬性傳遞數據
二、$emit、@on,事件觸發與事件監聽
三、狀態管理 vuex,或者 Vue.observale()進行狀態管理
四、ref引用獲取組件實例
五、$parent、$children,獲取當前組件的父組件、子組件
六、通過 provide / inject 來實現共享數據
我們主要看下 provide 和 inject 方式。先可以看官網描述:provide / inject
provider/inject:簡單的來說就是在父組件中通過provider來提供變量,然后在子組件中通過inject來注入變量。
這種方式可以避免在使用props傳值時,必須將每一個屬性都傳遞給子組件的寫法,當使用的公共組件不確定會被傳遞什么值的時候,使用這種寫法非常方便。
//父組件示例
<template>
<div>
<childCom></childCom>
</div>
</template>
<script> import childCom from '../components/childCom' export default { name: "Parent", provide: { //重要一步,在父組件中注入一個變量 msg: "demo" }, components:{ childCom } }
//子組件示例,這個不管嵌套了多少層,都能請求到父組件中注冊的變量
<template>
<div> {{msg}} </div>
</template>
<script> export default { name: "childCom", inject: ['msg'],//子孫組件中使用inject接住變量即可
} </script>
1、基本使用方法:
// 祖先組件 提供foo //第一種
export default { name: "grandfather", provide(){ return{ foo:'halo' } }, } //第二種
export default { name: "grandfather", provide:{ foo:'halo~~~~' }, }
//后代組件 注入foo
export default { inject:['foo'], }
上面兩種的區別:如果你只是傳一個字符串,像上面的‘halo’,那么是沒有區別的,后代都能讀到。如果你需要傳一個對象(如下所示代碼),那么第二種是傳不了的,后代組件拿不到數據。所以建議只寫第一種
//當你傳遞對象給后代時
provide(){ return{ test:this.msg } },
注意:一旦注入了某個數據,比如上面示例中的 msg、foo,那這個組件中就不能再聲明 msg、foo 這個數據了,因為它已經被父級占有。
2、實例
我們來看個例子:孫組件 D、E 和 F 獲取 A 組件傳遞過來的 color 值,並能實現數據響應式變化,即 A 組件的 color 變化后,組件 D、E、F 跟着變(核心代碼如下:)
方法一:provide 祖先組件的實例,然后在子孫組件中注入依賴,這樣就可以在子孫組件中直接修改祖先組件的實例的屬性,不過這種方法有個缺點就是這個實例上掛載很多沒有必要的東西比如 props,methods
方法二:使用 2.6 最新 API Vue.observable 優化響應式 provide(推薦)
// A 組件
<div>
<h1>A 組件</h1>
<button @click="() => changeColor()">改變color</button>
<ChildrenB />
<ChildrenC />
</div> ...... data() { return { color: "blue" }; }, // provide() { // return { // theme: { // color: this.color //這種方式綁定的數據並不是可響應的 // } // 即A組件的color變化后,組件D、E、F不會跟着變 // }; // },
provide() { return { theme: this //方法一:提供祖先組件的實例 }; }, methods: { changeColor(color) { if (color) { this.color = color; } else { this.color = this.color === "blue" ? "red" : "blue"; } } }
// F 組件
<template functional>
<div class="border2">
<h3 :style="{ color: injections.theme.color }">F 組件</h3>
</div>
</template>
<script> export default { inject: { theme: { //函數式組件取值不一樣 default: () => ({}) } } }; </script>
方案1的方法就是因為provide提供的數據不是雙向綁定的,所以祖父組件變化,孫組件獲取的inject注入的值不變。為了實現雙向綁定呢,就采用了直接在provide里把整個祖父組件的實例都傳給了孫組件,那么祖父組件的值變化了,孫組件從祖父組件實例里拿的值也變化了。缺點就是這個祖父組件實例掛載了太多不需要的東西。
// 方法二:使用2.6最新API Vue.observable 優化響應式 provide provide () { this.theme = Vue.observable({ color: "blue" }); return { theme: this.theme }; }, methods: { changeColor(color) { if (color) { this.theme.color = color; } else { this.theme.color = this.theme.color === "blue" ? "red" : "blue"; } } }
方法三:provide里返回一個函數,獲取組件的動態數據。
實際情況中往往我們需要的是共享父組件里面的動態數據,這些數據可能來自於data 或者 store。 就是說父組件里面的數據發生變化之后,需要同步到子孫組件里面。這時候該怎么做呢?
我想的是將一個函數賦值給provide的一個值,這個函數返回父組件的動態數據,然后在子孫組件里面調用這個函數。實際上這個函數存儲了父組件實例的引用,所以每次子組件都能獲取到最新的數據。代碼長下面的樣子:
Parent組件:
<template>
<div class="parent-container"> Parent組件 <br/>
<button type="button" @click="changeName">改變name</button>
<br/> Parent組件中 name的值: {{name}} <Child v-bind="{name: 'k3vvvv'}" />
</div>
</template>
<style scoped> .parent-container { padding: 30px; border: 1px solid burlywood; } </style>
<script> import Child from './Child' export default { name: 'Parent', data () { return { name: 'Kevin' } }, methods: { changeName (val) { this.name = 'Kev' } }, provide: function () { return { nameFromParent: this.name, getReaciveNameFromParent: () => this.name } },
components: { Child } } </script>
GrandSon組件:
<template>
<div class="grandson-container"> Grandson組件 <br/> {{nameFromParent}} <br/> {{reactiveNameFromParent}} </div>
</template>
<style scoped> .grandson-container { padding: 30px; border: 1px solid burlywood; } </style>
<script> export default { inject: ['nameFromParent', 'getReaciveNameFromParent'], computed: { reactiveNameFromParent () { return this.getReaciveNameFromParent() } }, watch: { 'reactiveNameFromParent': function (val) { console.log('來自Parent組件的name值發生了變化', val) } }, mounted () { console.log(this.nameFromParent, 'nameFromParent') } } </script>
第一個kevin 是來自於非響應式的 provide變量 nameFromParent
第二個來自於reactiveNameFromParent
,隨着祖先組件變化而變化了