組件之間通信, 主要存在于下面三種情況:
- 父子組件之間(Index-A惊橱、Index-B...)
- 兄弟組件之間(A-B)
- 隔代關(guān)系組件之間(Index-C, Index-D)
那么具體他們之間如何進(jìn)行通信, 我們一一解答
首先先看下文件的目錄結(jié)構(gòu)
接下來看看具體的通信方式
1. props
& $emit
1.1 父?jìng)髯?props
現(xiàn)在我們要從Index
頁(yè)面給A
頁(yè)面?zhèn)鬟f一個(gè)數(shù)組list
// index.vue
<template>
<div>
<A :list="list" />
</div>
</template>
<script>
import A from "./components/A";
export default {
name: "Index",
components: {
A
},
data() {
return {
list: ["html", "css", "js"],
};
},
},
</script>
// A.vue
<template>
<div>
<h2>Page A</h2>
<ul v-for="(item, index) in list" :key="index">
<li>{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
name: "A",
props: ["list"],
};
</script>
效果圖如下, 此時(shí)list
數(shù)組以自上而下的一種方式從Index
頁(yè)面?zhèn)鬟f給A
組件, 且props
只可以從上一級(jí)想下一級(jí)傳輸, 即所謂的單向數(shù)據(jù)流
1.2 子傳父 $emit
那A
組件想給Index
頁(yè)面?zhèn)魉蛿?shù)據(jù)應(yīng)該如何操作? 關(guān)于子組件給父組件傳值, 一般都是通過一個(gè)事件搭配$emit
進(jìn)行傳輸
// A.vue
<template>
<div>
<h2>Page A</h2>
<ul v-for="(item, index) in list" :key="index">
<!-- 定義事件 -->
<li @click="onItemClick(item)">{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
name: "A",
props: ["list"],
methods: {
onItemClick(item) {
this.$emit("on-item-click", `'${item}' from page A`);
},
},
};
</script>
父組件監(jiān)聽子組件上的事件名on-item-click
// index.vue
<template>
<div>
<!-- 監(jiān)聽 -->
<A :list="list" @on-item-click="handleItemClick" />
</div>
</template>
<script>
import A from "./components/A";
export default {
name: "Index",
components: {
A,
},
data() {
return {
list: ["html", "css", "js"],
};
},
methods: {
handleItemClick(value) {
console.log(`In page Index get ${value}`);
},
},
};
</script>
此時(shí), 點(diǎn)擊li
元素的時(shí)候, 我們就可以在Index
頁(yè)面獲取到A
頁(yè)面?zhèn)鬟f過來的值
2. $children
& $parent
$parent
的類型為: Vue instance$children
的類型為: Array<Vue instance>, 需要注意$children
并不保證順序彭则,也不是響應(yīng)式的
因?yàn)檫@兩個(gè)API拿到的都是vue實(shí)例, 所以可以訪問父組件或子組件身上變量, 方法等; 使用方式如下:
在A
頁(yè)面中定義了一個(gè)變量msg
, 通過$parent
獲取父組件中的數(shù)據(jù)
// A.vue
<template>
<div>
<h2>Page A</h2>
<p>{{ msg }}</p>
<ul v-for="(item, index) in $parent.list" :key="index">
<li @click="onItemClick(item)">{{ item }}</li>
</ul>
<hr />
</div>
</template>
<script>
export default {
name: "A",
data() {
return {};
},
// 獲取Index中的list
mounted() {
console.log('A mounted', this.$parent.list);
},
};
</script>
現(xiàn)在我們?cè)诟附M件中通過$children
獲取到A
頁(yè)面中的msg
并修改
// Index.vue
<template>
<div>
<A :list="list" @on-item-click="handleItemClick" />
<button @click="handleChange">change A's msg</button>
</div>
</template>
<script>
import A from "./components/A";
export default {
name: "Index",
components: {
A,
},
data() {
return {
list: ["html", "css", "js"],
};
},
methods: {
handleItemClick(value) {
console.log(`In page Index get ${value}`);
},
handleChange() {
console.log((this.$children[0].msg = "A's data is changed"));
},
},
};
</script>
3. ref
ref
如果用在普通的DOM元素上, 引用指向的就是DOM元素本身; 如果使用在組件上, 引用指向的就是該組件的實(shí)例
// index.vue
<template>
<div>
<A ref="componentA" :list="list" @on-item-click="handleItemClick" />
<p ref="p">p標(biāo)簽</p>
</div>
</template>
<script>
import A from "./components/A";
export default {
name: "Index",
components: {
A,
},
mounted() {
console.log("componentA ref", this.$refs.componentA);
console.log("p ref", this.$refs.p);
},
};
</script>
下面是打印的結(jié)果
當(dāng)
ref
和v-for
一起使用的時(shí)候,$refs
得到的是一個(gè)數(shù)組
4. provide
& inject
-
provide:
Object | () => Object
-
inject:
Array<string> | { [key: string]: string | Symbol | Object }
這對(duì)選項(xiàng)需要一起使用, 允許一個(gè)祖先組件向所有后代注入一個(gè)依賴, 不論層級(jí)有多深, 并在其上下游關(guān)系成立的時(shí)間里始終生效
我們現(xiàn)在來看一下具體的例子: Index -> A -> C
先在Index
中提供數(shù)據(jù)
// index.vue
export default {
name: "Index",
components: {
A,
},
provide: {
name: "name from Index",
},
data() {
return {
list: ["html", "css", "js"],
};
},
methods: {
handleItemClick(value) {
console.log(`In page Index get ${value}`);
},
handleChange() {
console.log((this.$children[0].msg = "A's data is changed"));
},
},
};
然后在C
組件中通過inject
獲取值
// C.vue
export default {
name: "C",
inject: {
value: "name",
data: {
from: "data1",
default: "1",
},
},
mounted() {
// Index 中并沒有在 provide 中提供 data1 變量, 所以C組件會(huì)取默認(rèn)值
console.log("C", this.value, this.data); // C name from Index 1
},
};
5. EventBus($emit, $on, $off)
這種方式是通過創(chuàng)建一個(gè)空的Vue
實(shí)例作為事件總線, 用它來觸發(fā)事件, 監(jiān)聽事件, 解除事件, 從而實(shí)現(xiàn)任何組件之間的通信, 然后當(dāng)項(xiàng)目逐漸擴(kuò)大, 這種通信方式還是不建議選擇, 難以維護(hù)
使用EventBus
來通信主要有以下幾個(gè)步驟
5.1 創(chuàng)建一個(gè)事件總線并將其導(dǎo)出
// event-bus.js
import Vue from "vue";
export const EventBus = new Vue();
5.2 發(fā)送一個(gè)事件
主要有Index厨钻、A牧嫉、B、C
幾個(gè)頁(yè)面
// index.vue
<template>
<div>
<A />
<B />
</div>
</template>
// A.vue
<template>
<div>
<h2 @click="handleClick">Page A</h2>
<C />
</div>
</template>
<script>
import C from "./C";
// 引入
import { EventBus } from "../event-bus.js";
export default {
name: "A",
components: {
C,
},
methods: {
// 觸發(fā)事件
handleClick() {
EventBus.$emit("transfer-by-event-bus", "eventBus data");
},
},
};
</script>
5.3 監(jiān)聽事件, 接收數(shù)據(jù)
// B.vue
<template>
<div>
<h2>Page B</h2>
</div>
</template>
<script>
import { EventBus } from "../event-bus";
export default {
name: "B",
mounted() {
// 監(jiān)聽事件
EventBus.$on("transfer-by-event-bus", value => console.log("B", value)); // B eventBus data
},
};
</script>
// C.vue
<template>
<h5>C</h5>
</template>
<script>
import { EventBus } from "../event-bus";
export default {
name: "C",
mounted() {
EventBus.$on("transfer-by-event-bus", value => console.log("C", value)); // C eventBus data
},
};
</script>
5.4 移除事件
import { eventBus } from 'event-bus.js'
EventBus.$off('transfer-by-event-bus', {})
6. $attrs & $listeners
6.1 $attrs
包含了父作用域中不作為 prop 被識(shí)別 (且獲取) 的 attribute 綁定 (
class
和style
除外)泣港。當(dāng)一個(gè)組件沒有聲明任何 prop 時(shí)售躁,這里會(huì)包含所有父作用域的綁定 (class
和style
除外),并且可以通過v-bind="$attrs"
傳入內(nèi)部組件——在創(chuàng)建高級(jí)別的組件時(shí)非常有用
我們先通過Index
頁(yè)面向B
組件傳遞name, sex, age
三個(gè)值
// index.vue
<template>
<div>
<B :name="name" :sex="sex" :age="age" @on-click="handleClick" />
</div>
</template>
<script>
import B from "./components/B";
export default {
name: "Index",
components: {
B,
},
data() {
return {
name: "Lily",
sex: "female",
age: "20",
};
},
};
</script>
然后再B
組件中我們打印一下$attrs
, 看看能獲取什么值? 可以看見$attrs
返回的是一個(gè)對(duì)象, 且鍵值對(duì)就是我們?cè)?code>Index頁(yè)面?zhèn)鹘oB
組件的值
<template>
<div>
<h2>Page B</h2>
</div>
</template>
<script>
export default {
name: "B",
mounted() {
console.log(this.$attrs); // {name: "Lily", sex: "female", age: "20"}
},
如果此時(shí)我們?cè)?code>B組件的props
中接收一個(gè)變量, 看看有何變化? 打印出來的值不包含props
獲取的name
字段
<template>
<div>
<h2>Page B</h2>
</div>
</template>
<script>
export default {
name: "B",
props: {
name: String,
},
mounted() {
console.log(this.$attrs); // {sex: "female", age: "20"}
},
現(xiàn)在需要把這幾個(gè)變量再繼續(xù)傳給B
組件的子組件D
, 我們直接通過v-bind="$attrs"
即可
// B.vue
<template>
<div>
<h2>Page B</h2>
<D v-bind="$attrs" />
</div>
</template>
<script>
import D from "./D";
export default {
name: "B",
components: {
D,
},
props: {
name: String,
},
mounted() {
console.log(this.$attrs); // {sex: "female", age: "20"}
},
D
組件同樣獲取到了這幾個(gè)值
// D.vue
<template>
<h5>D</h5>
</template>
<script>
export default {
name: "D",
mounted() {
console.log(this.$attrs); // {sex: "female", age: "20"}
},
};
</script>
6.2 $listeners
包含了父作用域中的 (不含
.native
修飾器的)v-on
事件監(jiān)聽器墨微。它可以通過v-on="$listeners"
傳入內(nèi)部組件——在創(chuàng)建更高層次的組件時(shí)非常有用
當(dāng)子組件需要調(diào)用父組件中的方法, 我們就可以通過$listeners
來調(diào)用, 但前提是方法名必須在父組件中被定義
我們實(shí)操一下, 先在Index
頁(yè)面的分別定義兩個(gè)事件
<template>
<div>
<B
@click1="handeClick1"
@click2="handeClick2"
/>
</div>
</template>
<script>
import B from "./components/B";
export default {
name: "Index",
components: {
B,
},
data() {
return {};
},
methods: {
handeClick1() {
console.log("1");
},
handeClick2() {
console.log("2");
},
},
};
</script>
子組件B
調(diào)用方法如下, 現(xiàn)在我們分別點(diǎn)擊one
, two
兩個(gè)文本就可以分別打印出1
, 2
// B.vue
<template>
<div>
<h2 @click="$listeners.click1">one</h2>
<h2 @click="$listeners.click2">two</h2>
</div>
</template>
<script>
export default {
name: "B",
data() {
return {};
},
};
</script>
如果再向下傳遞給B
的子組件D
也是沒有問題的, 我們只需要將$listeners
傳下就可以了
// B.vue
<template>
<div>
<h2 @click="$listeners.click1">one</h2>
<h2 @click="$listeners.click2">two</h2>
<D v-on="$listeners" />
</div>
</template>
D
組件也像B
組件那樣調(diào)用事件即可
// D.vue
<template>
<h5 @click="$listeners.click1">D</h5>
</template>
7. Vuex
Vuex
想必大家應(yīng)該很熟悉, 它是一個(gè)專為Vue.js
應(yīng)用程序開發(fā)的狀態(tài)管理模式, 它讓開發(fā)者能夠聚焦于數(shù)據(jù)的更新而不是數(shù)據(jù)的傳遞
Vuex
主要有以下幾個(gè)模塊
-
state
: 用于數(shù)據(jù)的存儲(chǔ), 是store
中的唯一數(shù)據(jù)源 -
getters
: 同vue
中的計(jì)算屬性, 基于state
數(shù)據(jù)進(jìn)行二次包裝來獲取符合條件的數(shù)據(jù) -
mutations
: 處理同步事件, 是唯一更改 store 中狀態(tài)的方法 -
actions
: 可包含異步操作, 用于提交mutation
, 不可直接變更狀態(tài) -
modules
: 類似于命名空間, 用于項(xiàng)目中將各個(gè)模塊的狀態(tài)分開定義和操作, 便于維護(hù)
8. 總結(jié)
我們現(xiàn)在就開頭說的三種場(chǎng)景再進(jìn)行一次總結(jié), 但是我們還是需要根據(jù)當(dāng)下的場(chǎng)景選擇合適的通信方式~
- 父子組件之間:
props & $emit
,$chilren & $parent
,ref
,EventBus
,provide & inject
,Vuex
- 兄弟組件之間:
Vuex
,EventBus
- 隔代關(guān)系組件之間:
EventBus
,$attrs & $listeners
,provide & inject
,Vuex