Vue中組件通訊的方式有很多種,每一種應用的場景可能都有一些不一樣橙依,我們需要在合適的場景下選擇合適的通訊方式。
父子組件間通訊:props和emit护蝶、emit占哟、emit心墅、parent、refs和refs和refs和children榨乎、v-model
兄弟組件間通訊:事件總線嗓化、Vuex、localStorage
隔代組件間通訊:provide和inject
無相關組件間通訊:事件總線谬哀、Vuex刺覆、localStorage
props 和$emit
父組件傳值給子組件
// src/views/parent.vue
<template>
<div class="parent-box">
<p>父級組件</p>
<div>
<button @click="changeMsg">更改數(shù)據(jù)</button>
</div>
<child1 :msg="msg"></child1>
<child2 :msg="msg"></child2>
</div>
</template>
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
data() {
return {
msg: "我是父組件的數(shù)據(jù)",
};
},
components: {
child1,
child2,
},
methods: {
// 點擊按鈕更改數(shù)據(jù)
changeMsg() {
this.msg = "變成小豬課堂";
},
},
};
</script>
我們將父組件中的msg通過:msg="msg"的方式傳遞給子組件,并且點擊按鈕的時候會修改父組件中的msg史煎。
// src/views/child1.vue
<template>
<div class="child-1">
<p>child1組件</p>
<div>
<p>parent組件數(shù)據(jù):{{ msg }}</p>
</div>
</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
default: "",
},
},
};
</script>
子組件通過props屬性的方式接收父組件傳來的數(shù)據(jù)谦屑。
當我們點擊按鈕的時候驳糯,父組件的數(shù)據(jù)發(fā)生變化,子組件接收的數(shù)據(jù)也跟著發(fā)生了變化氢橙。
注意::msg="msg"接收的msg是一個變量酝枢,可以參考bind的使用原理,不加:則接收的就是一個字符串悍手。
子組件傳值給父組件
子組件可以通過$emit自定義事件的方式向父組件傳遞值帘睦,父組件需要監(jiān)聽該事件來進行接收子組件傳來的值。
父組件示例代碼:
// src/views/parent.vue
<template>
<div class="parent-box">
<p>父級組件</p>
<div>
<button @click="changeMsg">更改數(shù)據(jù)</button>
</div>
<div>子組件數(shù)據(jù):{{ childData }}</div>
<child1 :msg="msg" @childData="childData"></child1>
<child2 :msg="msg"></child2>
</div>
</template>
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
data() {
return {
msg: "我是父組件的數(shù)據(jù)",
childData: "",
};
},
components: {
child1,
child2,
},
methods: {
changeMsg() {
this.msg = "變成小豬課堂";
},
// 監(jiān)聽子組件事件
childData(data) {
this.childData = data;
},
},
};
</script>
子組件示例代碼:
// src/views/child1.vue
<template>
<div class="child-1">
<p>child1組件</p>
<div>
<button @click="sendData">傳遞數(shù)據(jù)給父組件</button>
</div>
<div>
<p>parent組件數(shù)據(jù):{{ msg }}</p>
</div>
</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
default: "",
},
},
methods: {
// 點擊按鈕坦康,使用$emit向父組件傳遞數(shù)據(jù)
sendData() {
this.$emit("childData", "我是子組件數(shù)據(jù)");
},
},
};
</script>
我們在父組件中通過@childData="getChildData"的方式來監(jiān)聽childData事件竣付,從而獲取子組件傳遞的數(shù)據(jù),子組件中通過點擊按鈕觸發(fā)$emit事件向父組件傳遞數(shù)據(jù)滞欠。當我們點擊按鈕“傳遞數(shù)據(jù)給父組件”時古胆,父組件便可以獲取到數(shù)據(jù)。
$parent獲取父組件值
這種方式可以讓子組件非常方便的獲取父組件的值筛璧,不僅僅包括數(shù)據(jù)逸绎,還可以是方法。
子組件示例代碼:
// src/views/child1.vue
<template>
<div class="child-1">
<p>child1組件</p>
<div>
<button @click="sendData">傳遞數(shù)據(jù)給父組件</button>
</div>
<div>
<button @click="getParentData">使用$parent</button>
</div>
<div>
<p>parent組件數(shù)據(jù):{{ msg }}</p>
</div>
</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
default: "",
},
},
methods: {
sendData() {
this.$emit("childData", "我是子組件數(shù)據(jù)");
},
// 通過$parent方式獲取父組件值
getParentData() {
console.log("父組件", this.$parent);
},
},
};
</script>
點擊“使用parent”按鈕時夭谤,通過parent”按鈕時棺牧,通過parent獲取父組件的屬性或數(shù)據(jù)。
我們可以看到控制臺打印出了父組件的所有屬性朗儒,不僅僅包含了data數(shù)據(jù)颊乘,還有里面定義的一些方法等。
refs獲取子組件值
這兩種方式和$parent非常的類似采蚀,它們可以直接獲取子組件的相關屬性或方法疲牵,不僅限于數(shù)據(jù)承二。
父組件示例代碼:
// src/views/parent.vue
<template>
<div class="parent-box">
<p>父級組件</p>
<div>
<button @click="changeMsg">更改數(shù)據(jù)</button>
</div>
<div>
<button @click="getChildByRef">使用$children和$refs</button>
</div>
<div>子組件數(shù)據(jù):{{ childData }}</div>
<child1 ref="child1" :msg="msg" @childData="getChildData"></child1>
<child2 :msg="msg"></child2>
</div>
</template>
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
data() {
return {
msg: "我是父組件的數(shù)據(jù)",
childData: "",
};
},
components: {
child1,
child2,
},
methods: {
changeMsg() {
this.msg = "變成小豬課堂";
},
// 監(jiān)聽子組件的自定義事件
getChildData(data) {
this.childData = data;
},
// 使用$chilren和$refs獲取子組件
getChildByRef() {
console.log("使用$children", this.$children);
console.log("使用$refs", this.$refs.child1);
},
},
};
</script>
上段代碼中榆鼠,我們點擊按鈕,分別通過children和children和children和refs的方式獲取到了子組件亥鸠,從而拿到子組件數(shù)據(jù)妆够。需要注意的是,children會返回當前組件所包含的所有子組件數(shù)組负蚊,使用children會返回當前組件所包含的所有子組件數(shù)組神妹,使用children會返回當前組件所包含的所有子組件數(shù)組,使用refs時家妆,需要在子組件上添加ref屬性鸵荠,有點類似于直接獲取DOM節(jié)點的操作。
使用
listeners
arrts的方式乍楚,畢竟Vuex還是比較重。
當父組件傳遞了很多數(shù)據(jù)給子組件時届慈,子組件沒有聲明props來進行接收徒溪,那么子組件中的attrs屬性就包含了所有父組件傳來的數(shù)據(jù)(除開已經(jīng)props聲明了的),子組件還可以使用v?bind="attrs屬性就包含了所有父組件傳來的數(shù)據(jù)(除開已經(jīng)props聲明了的)金顿,子組件還可以使用v-bind="attrs屬性就包含了所有父組件傳來的數(shù)據(jù)(除開已經(jīng)props聲明了的)臊泌,子組件還可以使用v?bind="attrs"的形式向它的子組件(孫子組件)傳遞數(shù)據(jù),孫子組件使用$attrs的方式和它的父組件原理類似串绩。
$attrs的使用
我們在parent父組件中多傳一點數(shù)據(jù)給child1組件缺虐。
parent組件示例代碼:
// src/views/parent.vue
<template>
<div class="parent-box">
<p>父級組件</p>
<child1
ref="child1"
:msg="msg"
:msg1="msg1"
:msg2="msg2"
:msg3="msg3"
:msg4="msg4"
@childData="getChildData"
></child1>
</div>
</template>
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
data() {
return {
msg: "我是父組件的數(shù)據(jù)",
msg1: "parent數(shù)據(jù)1",
msg2: "parent數(shù)據(jù)2",
msg3: "parent數(shù)據(jù)3",
msg4: "parent數(shù)據(jù)4",
childData: "",
};
},
components: {
child1,
child2,
}
};
</script>
這里我們刪除了一些本節(jié)用不到的代碼,大家需要注意一下礁凡。
child1組件示例代碼:
// src/views/child1.vue
<template>
<div class="child-1">
<p>child1組件</p>
<!-- 子組件child1-child -->
<child1-child v-bind="$attrs"></child1-child>
</div>
</template>
<script>
import Child1Child from "./child1-child";
export default {
components: {
Child1Child,
},
props: {
msg: {
type: String,
default: "",
},
},
mounted() {
console.log("child1組件獲取$attrs", this.$attrs);
}
};
</script>
上段代碼中我們的parent父組件傳遞了5個數(shù)據(jù)給子組件:msg高氮、msg1、msg2顷牌、msg3剪芍、msg4。但是在子組件中的props屬性里面窟蓝,我們只接收了msg罪裹。然后我們在子組件mounted中打印了$attrs,發(fā)現(xiàn)恰好少了props接收過的msg數(shù)據(jù)运挫。
當我們在child1組件中使用attrs接收了組件后状共,可以使用v?bind="attrs接收了組件后,可以使用v-bind="attrs接收了組件后谁帕,可以使用v?bind="attrs"的形式在傳遞給它的子組件child1-child峡继,上段代碼中我們已經(jīng)加上了v-bind。
child1-child組件示例代碼:
// src/views/child1-child.vue
<template>
<div class="child1-child">
<p>我是孫子組件child1-child</p>
</div>
</template>
<script>
export default {
props: {
msg1: {
type: String,
default: "",
},
},
mounted() {
console.log("child1-child組件$attrs", this.$attrs);
},
};
</script>
我們發(fā)現(xiàn)child1-child組件中打印的$attrs中少了msg1匈挖,因為我們已經(jīng)在props中接收了msg1碾牌。
$listeners 的使用
listeners屬性和attrs屬性和類型,只是它們傳遞的東西不一樣儡循。
當父組件在子組件上定義了一些自定義的非原生事件時舶吗,在子組件內部可以通過
listeners用來傳遞非原生事件,我們在child1組件中打印一下看看腹侣。
child1組件示例代碼:
// src/views/child1.vue
mounted() {
console.log("child1組件獲取$attrs", this.$attrs);
console.log("child1組件獲取$listeners", this.$listeners);
},
可以發(fā)現(xiàn)輸出了childData方法呵扛,這是我們在它的父組件自定義的監(jiān)聽事件。除次之外筐带,$listeners可以通過v-on的形式再次傳遞給下層組件今穿。
child1組件示例代碼:
// src/views/child1.vue
<template>
<div class="child-1">
<p>child1組件</p>
<div>
<button @click="sendData">傳遞數(shù)據(jù)給父組件</button>
</div>
<div>
<button @click="getParentData">使用$parent</button>
</div>
<div>
<p>parent組件數(shù)據(jù):{{ msg }}</p>
</div>
<!-- 子組件child1-child -->
<child1-child v-bind="$attrs" v-on="$listeners"></child1-child>
</div>
</template>
child1-child組件示例代碼:
// src/views/child1-child.vue
mounted() {
console.log("child1-child組件$attrs", this.$attrs);
console.log("child1-child組件$listerners", this.$listeners);
},
可以看到在child1-child孫子組件中也獲得了parent父組件中的childData自定義事件。使用listeners的好處在于:如果存在多層級組件伦籍,無需使用listeners的好處在于:如果存在多層級組件蓝晒,無需使用listeners的好處在于:如果存在多層級組件,無需使用emit的方式逐級向上觸發(fā)事件帖鸦,只需要使用$listerners就可以得到父組件中的自定義事件芝薇,相當于偷懶了。
inheritAttrs
父組件傳遞了很多數(shù)據(jù)給子組件作儿,子組件的props沒有完全接收洛二,那么父組件傳遞的這些數(shù)據(jù)就會渲染到HTML上,我們可以給子組件設置inheritAttrs 為false攻锰,避免這樣渲染晾嘶。
child1組件示例代碼:
// src/views/child1.vue
props: {
msg: {
type: String,
default: "",
},
},
inheritAttrs: false,
總結
listeners:用來傳遞事件妒蛇,出了原生事件机断,它也是一個對象。
attrs和attrs和attrs和listeners這兩個屬性可以解決多層組件之間數(shù)據(jù)和事件傳遞的問題绣夺。
inheritAttrs解決未使用props接收的數(shù)據(jù)的屬性渲染吏奸。
自定義事件:事件總線
在我們做項目的時候,會發(fā)現(xiàn)不相關的組件之間的數(shù)據(jù)傳遞是較為麻煩的陶耍,比如兄弟組件奋蔚、跨級組件,在不使用Vuex情況下物臂,我們可以使用自定義事件(也可以稱作事件中心)的方式來實現(xiàn)數(shù)據(jù)傳遞旺拉。
事件中心的思想也比較簡單:中間中心主要就兩個作用:觸發(fā)事件和監(jiān)聽事件产上。假如兩個組件之間需要傳遞數(shù)據(jù)棵磷,組件A可以觸發(fā)事件中心的事件,組件B監(jiān)聽事件中心的事件晋涣,從而讓兩個組件之間產(chǎn)生關聯(lián)仪媒,實現(xiàn)數(shù)據(jù)傳遞。
實現(xiàn)步驟:
為了演示簡單,我們在全局注冊一個事件中心算吩,修改main.js留凭。
main.js代碼如下:
// src/main.js
Vue.config.productionTip = false
Vue.prototype.$EventBus = new Vue()
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
child1組件示例代碼:
<template>
<div class="child-1">
<p>child1組件</p>
<div>
<button @click="toChild2">向child2組件發(fā)送數(shù)據(jù)</button>
</div>
</div>
</template>
<script>
import Child1Child from "./child1-child";
export default {
methods: {
// 通過事件總線向child2組件發(fā)送數(shù)據(jù)
toChild2() {
this.$EventBus.$emit("sendMsg", "我是child1組件發(fā)來的數(shù)據(jù)");
},
},
};
</script>
child1組件中調用EventBus.EventBus.emit向事件中心添加sendMsg事件,這個用法有點類似與props和$emit的關系偎巢。
child2組件2示例代碼:
// src/views/child1.vue
<template>
<div class="child-2">
<p>child2組件</p>
<div>
<p>parent組件數(shù)據(jù):{{ msg }}</p>
</div>
</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
default: "",
},
},
mounted() {
this.$EventBus.$on("sendMsg", (msg) => {
console.log("接收到child1發(fā)送來的數(shù)據(jù)", msg);
});
},
};
</script>
當我們點擊child1組件中的按鈕時蔼夜,就會觸發(fā)sendMsg事件,在child2組件中我們監(jiān)聽了該事件压昼,所以會接收到child1組件發(fā)來的數(shù)據(jù)求冷。
事件中心實現(xiàn)數(shù)據(jù)傳遞的這種方式,其實就是一個發(fā)布者和訂閱者的模式窍霞,這種方式可以實現(xiàn)任何組件之間的通信匠题。
provide和inject
這兩個是在Vue2.2.0新增的API,provide和inject需要在一起使用但金。它們也可以實現(xiàn)組件之間的數(shù)據(jù)通信韭山,但是需要確保組件之間是父子關系。
簡單一句話:父組件可以向子組件(無論層級)注入依賴冷溃,每個子組件都可以獲得這個依賴钱磅,無論層級。
parent示例代碼:
// src/views/parent.vue
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
provide() {
return { parentData: this.msg };
},
data() {
return {
msg: "我是父組件的數(shù)據(jù)",
msg1: "parent數(shù)據(jù)1",
msg2: "parent數(shù)據(jù)2",
msg3: "parent數(shù)據(jù)3",
msg4: "parent數(shù)據(jù)4",
childData: "",
};
},
components: {
child1,
child2,
},
};
</script>
child1-child組件示例代碼:
// src/views/child1-child.vue
<template>
<div class="child1-child">
<p>我是孫子組件child1-child</p>
<p>parent組件數(shù)據(jù):{{parentData}}</p>
</div>
</template>
<script>
export default {
inject: ["parentData"],
props: {
msg1: {
type: String,
default: "",
},
},
mounted() {
console.log("child1-child組件$attrs", this.$attrs);
console.log("child1-child組件$listerners", this.$listeners);
console.log("child1-child組件獲取parent組件數(shù)據(jù)", this.parentData)
},
};
</script>
通過provide和inject結合的方式似枕,我們在child1-child組件中獲取到了parent組件中的數(shù)據(jù)续搀。如果你下來嘗試過的話,可能會發(fā)現(xiàn)一個問題菠净,此時數(shù)據(jù)不是響應式禁舷,也就是parent組件更改了數(shù)據(jù),child1-child組件中的數(shù)據(jù)不會更新毅往。
想要變?yōu)轫憫降那A覀冃枰薷囊幌聀rovide傳遞的方式。
parent代碼如下:
// src/views/parent.vue
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
provide() {
return { parentData: this.getMsg };
},
data() {
return {
msg: "我是父組件的數(shù)據(jù)",
msg1: "parent數(shù)據(jù)1",
msg2: "parent數(shù)據(jù)2",
msg3: "parent數(shù)據(jù)3",
msg4: "parent數(shù)據(jù)4",
childData: "",
};
},
components: {
child1,
child2,
},
methods: {
// 返回data數(shù)據(jù)
getMsg() {
return this.msg;
},
},
};
</script>
這個時候我們會發(fā)現(xiàn)數(shù)據(jù)變?yōu)轫憫降牧恕?/p>
porvide和inject的原理可以參考下圖:
Vuex和localStorage
Vuex:
Vuex是狀態(tài)管理器攀唯,它存儲的數(shù)據(jù)不是持久化存儲洁桌,一旦刷新頁面或者關閉項目數(shù)據(jù)便不見了。
Vuex存儲的數(shù)據(jù)是響應式的侯嘀。
localstorage:
loacalStorage是HTML5中的一種數(shù)據(jù)存儲方式另凌,持久化存儲,存儲的數(shù)據(jù)不是響應式的戒幔。
v-model
v-model是vue中的一個內置指令吠谢,它通常用在表單元素上以此來實現(xiàn)數(shù)據(jù)的雙向綁定,它的本質是v-on和v-bind的語法糖诗茎。在這里我們也可以借助它來實現(xiàn)某些場景下的數(shù)據(jù)傳遞工坊。注意,這兒的場景必須是父子組件。
parent組件示例代碼:
<template>
<div class="parent-box">
<p>父級組件</p>
<div>modelData: {{modelData}}</div>
<child2 :msg="msg" v-model="modelData"></child2>
<!-- 實際等同于 -->
<!-- <child2 v-bind:value="modelData" v-on:input="modelData=$event"></child2> -->
</div>
</template>
<script>
import child2 from "./child2.vue";
export default {
provide() {
return { parentData: this.getMsg };
},
data() {
return {
modelData: "parent組件的model數(shù)據(jù)"
};
},
components: {
child1,
},
};
</script>
child2組件示例代碼:
<template>
<div class="child-2">
<p>child2組件</p>
<div>
<button @click="confirm">修改v-model數(shù)據(jù)</button>
</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: "",
},
},
mounted() {
console.log("child2組件接收附件見v-model傳遞的數(shù)據(jù)", this.value);
},
methods: {
// 通過$emit觸發(fā)父組件的input事件王污,并將第二個參數(shù)作為值傳遞給父組件
confirm() {
this.$emit("input", "修改parent傳遞的v-model數(shù)據(jù)");
},
},
};
</script>
我們在父組件中使用v-model向child2子組件傳遞數(shù)據(jù)罢吃,子組件的props中使用默認的value屬性接收,在子組件中利用$emit觸發(fā)父組件中默認input事件昭齐,此時傳遞的數(shù)據(jù)便會在子組件和父組件中發(fā)生變化尿招,這就是數(shù)據(jù)雙向綁定。
如果想要更加詳細的學習v-model的使用阱驾,可以參考官網(wǎng)泊业。
作者:小豬課堂
鏈接:https://juejin.cn/post/7086735244992200734
來源:稀土掘金
著作權歸作者所有。商業(yè)轉載請聯(lián)系作者獲得授權啊易,非商業(yè)轉載請注明出處吁伺。