vue中組件之間的通信
組件可以有以下幾種關(guān)系:
[圖片上傳失敗...(image-8918e-1660823023573)]
A-B、B-C质况、B-D都是父子關(guān)系
C-D是兄弟關(guān)系
A-C结榄、A-D是隔代關(guān)系
不同使用場景囤捻,如何選擇有效的通信方式 ?vue
組件中通信的幾種方式 视哑?
1. props
★★
2. $emit/$on
★★ 事件總線
3. vuex
★★★
4.$parent/$children
5. $attrs/$listeners
6. provide/inject
★★★
vue中組件之間通信挡毅?
常見使用場景可以分為三類:
- 父子組件通信
- 兄弟組件通信
- 跨層組件通信
- 分幾種
- 使用
- 選用
最后說一下在項目中怎么選用:比如我們我們項目中只是涉及到簡單的數(shù)據(jù)數(shù)據(jù)傳遞選擇props跪呈,其次如果項目中需要保存狀態(tài)的時候選用vuex等等,只是說一個簡單例子庆械!
方法一、props
父組件A通過props
向子組件B傳遞值沐序, B組件傳遞A組件通過$emit
A組件通過v-on/@
觸發(fā)
1-1 父組件=>子組件傳值
// 父組件
<template>
<div id="app">
<Child v-bind:child="users"></Child> //前者自定義名稱便于子組件調(diào)用策幼,后者要傳遞數(shù)據(jù)名
</div>
</template>
<script>
import Child from "./components/Child" //子組件
export default {
name: 'App',
data(){
return{
users:["Eric","Andy","Sai"]
}
},
components:{
"Child":Child
}
}
</script>
// 子組件
<template>
<div class="hello">
<ul>
<li v-for="item in child">{{ item }}</li> //遍歷傳遞過來的值渲染頁面
</ul>
</div>
</template>
<script>
export default {
name: 'Hello World',
props:{
child:{ //這個就是父組件中子標簽自定義名字
type:Array, //對傳遞過來的值進行校驗
required:true //必添
}
}
}
</script>
總結(jié):父組件通過props向下傳遞數(shù)據(jù)給子組件特姐。
1-2子組件=>父組件傳值
// 子組件 Header.vue
<template>
<div>
<h1 @click="changeTitle">{{ title }}</h1> //綁定一個點擊事件
</div>
</template>
<script>
export default {
name: 'header',
data() {
return {
title:"Vue.js Demo"
}
},
methods:{
changeTitle() {
this.$emit("titleChanged","子向父組件傳值"); //自定義事件 傳遞值“子向父組件傳值”
}
}
}
</script>
// 父組件
<template>
<div id="app">
<header v-on:titleChanged="updateTitle"></header>
//與子組件titleChanged自定義事件保持一致
// updateTitle($event)接受傳遞過來的文字
<h2>{{ title }}</h2>
</div>
</template>
<script>
import Header from "./components/Header"
export default {
name: 'App',
data(){
return{
title:"傳遞的是一個值"
}
},
methods:{
updateTitle(e){ //聲明這個函數(shù)
this.title = e;
}
},
components:{
"app-header":Header,
}
}
</script>
總結(jié):子組件通過events給父組件發(fā)送消息唐含,實際上就是子組件把自己的數(shù)據(jù)發(fā)送到父組件捷枯。
方法二专执、$emit/$on => $bus
vue
實例 作為事件總線(事件中心)用來觸發(fā)事件和監(jiān)聽事件,可以通過此種方式進行組件間通信包括:父子組件攀痊、兄弟組件苟径、跨級組件
例:
創(chuàng)建bus文件
import Vue from 'vue'
export defult new Vue()
// gg組件
<template id="a">
<div>
<h3>gg組件</h3>
<button @click="sendMsg">將數(shù)據(jù)發(fā)送給dd組件</button>
</div>
</template>
<script>
import bus from './bus'
export default {
methods: {
sendMsg(){
bus.$emit('sendTitle','傳遞的值')
}
}
}
</script>
// dd組件
<template>
<div>
接收gg傳遞過來的值:{{msg}}
</div>
</template>
<script>
import bus from './bus'
export default {
data(){
return {
mag: ''
}
}
mounted(){
bus.$on('sendTitle',(val)=>{
this.mag = val
})
}
}
</script>
方法三棘街、vuex
[圖片上傳失敗...(image-97ac67-1660823023573)]
1-1 vuex介紹
Vuex
實現(xiàn)了一個單向數(shù)據(jù)流盒件,在全局擁有一個State
存放數(shù)據(jù),當組件要更改State
中的數(shù)據(jù)時恩沽,必須通過Mutation
提交修改信息罗心,Mutation
同時提供了訂閱者模式供外部插件調(diào)用獲取State
數(shù)據(jù)的更新。
而當所有異步操作(常見于調(diào)用后端接口異步獲取更新數(shù)據(jù))或批量的同步操作需要走Action
渤闷,但Action
也是無法直接修改State
的飒箭,還是需要通過Mutation
來修改State的數(shù)據(jù)。最后肩碟,根據(jù)State
的變化凸椿,渲染到視圖上脑漫。
1-2 vuex中核心概念
-
state
:vuex
的唯一數(shù)據(jù)源,如果獲取多個state
,可以使用...mapState
吨拍。export const store = new Vuex.Store({ // 注意Store的S大寫 <!-- 狀態(tài)儲存 --> state: { productList: [ { name: 'goods 1', price: 100 } ] } })
-
getter
: 可以將getter
理解為計算屬性密末,getter
的返回值根據(jù)他的依賴緩存起來跛璧,依賴發(fā)生變化才會被重新計算新啼。import Vue from 'vue' import Vuex from 'vuex'; Vue.use(Vuex) export const store = new Vuex.Store({ state: { productList: [ { name: 'goods 1', price: 100 }, ] }, // 輔助對象 mapGetter getters: { getSaledPrice: (state) => { let saleProduct = state.productList.map((item) => { return { name: '**' + item.name + '**', price: item.price / 2 } }) return saleProduct; } } })
// 獲取getter計算后的值 export default { data () { return { productList : this.$store.getters.getSaledPrice } } }
-
mutation
:更改vuex
的state
中唯一的方是提交mutation
都有一個字符串和一個回調(diào)函數(shù)燥撞。回調(diào)函數(shù)就是使勁進行狀態(tài)修改的地方色洞。并且會接收state
作為第一個參數(shù)payload
為第二個參數(shù)火诸,payload
為自定義函數(shù)荠察,mutation
必須是同步函數(shù)奈搜。// 輔助對象 mapMutations mutations: { <!-- payload 為自定義函數(shù)名--> reducePrice: (state, payload) => { return state.productList.forEach((product) => { product.price -= payload; }) } }
<!-- 頁面使用 --> methods: { reducePrice(){ this.$store.commit('reducePrice', 4) } }
-
action
:action
類似mutation
都是修改狀態(tài)馋吗,不同之處,action
提交的mutation
不是直接修改狀態(tài)
action
可以包含異步操作宏粤,而mutation
不行
action
中的回調(diào)函數(shù)第一個參數(shù)是context
灼卢,是一個與store
實例具有相同屬性的方法的對象
action
通過store.dispatch
觸發(fā),mutation
通過store.commit
提交
actions: { // 提交的是mutation蛇摸,可以包含異步操作 reducePriceAsync: (context, payload) => { setTimeout(()=> { context.commit('reducePrice', payload); // reducePrice為上一步mutation中的屬性 },2000) } }
// 輔助對象 mapActions methods: { reducePriceAsync(){ this.$store.dispatch('reducePriceAsync', 2) }, }
-
module
:由于是使用單一狀態(tài)樹赶袄,應(yīng)用的所有狀態(tài)集中到比較大的對象抠藕,當應(yīng)用變得非常復(fù)雜是,store
對象就有可能變得相當臃腫敬辣。為了解決以上問題溉跃,vuex允許我們將store
分割成模塊告抄,每個模塊擁有自己的state,mutation,action,getter
,甚至是嵌套子模塊從上至下進行同樣方式分割。const moduleA = { state: {...}, mutations: {...}, actions: {...}, getters: {...} } const moduleB = { state: {...}, mutations: {...}, actions: {...}, getters: {...} } const store = new Vuex.Store({ a: moduleA, b: moduleB }) store.state.a store.state.b
1-3 vuex中數(shù)據(jù)存儲 localStorage
vuex
是 vue
的狀態(tài)管理器龄糊,存儲的數(shù)據(jù)是響應(yīng)式的炫惩。但是并不會保存起來阿浓,刷新之后就回到了初始狀態(tài),具體做法應(yīng)該在vuex
里數(shù)據(jù)改變的時候把數(shù)據(jù)拷貝一份保存到localStorage
里面爸舒,刷新之后,如果localStorage
里有保存的數(shù)據(jù)鹊奖,取出來再替換store
里的state
忠聚。
例:
let defaultCity = "上海"
try {
// 用戶關(guān)閉了本地存儲功能唱捣,此時在外層加個try...catch
if (!defaultCity){
// f復(fù)制一份
defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))
}
}catch(e){
console.log(e)
}
export default new Vuex.Store({
state: {
city: defaultCity
},
mutations: {
changeCity(state, city) {
state.city = city
try {
window.localStorage.setItem('defaultCity', JSON.stringify(state.city));
// 數(shù)據(jù)改變的時候把數(shù)據(jù)拷貝一份保存到localStorage里面
} catch (e) {}
}
}
})
注意:vuex里震缭,保存的狀態(tài),都是數(shù)組党涕,而localStorage只支持字符串巡社,所以需要用JSON轉(zhuǎn)換:
JSON.stringify(state.subscribeList)<font color="red">// array -> string</font>
JSON.parse(window.localStorage.getItem("subscribeList"))<font color="red">// string -> array</font>
方法四、$attr/$listeners
1-1 簡介
多級組件嵌套需要傳遞數(shù)據(jù)時肥荔,通常使用的方法是通過vuex燕耿。但如果僅僅是傳遞數(shù)據(jù)潜圃,而不做中間處理舟茶,使用 vuex 處理,未免有點大材小用隧出。為此Vue2.4 版本提供了另一種方法----$attrs/$listeners
-
$attrs
:包含了父作用域中不被 prop 所識別 (且獲取) 的特性綁定 (class 和 style 除外)阀捅。當一個組件沒有聲明任何 prop 時,這里會包含所有父作用域的綁定 (class 和 style 除外)圆雁,并且可以通過 v-bind="$attrs" 傳入內(nèi)部組件帆谍。通常配合 interitAttrs 選項一起使用。 -
$listeners
:包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監(jiān)聽器烈涮。它可以通過 v-on="$listeners" 傳入內(nèi)部組件
例:
// index.vue
<template>
<div>
<h2>王者峽谷</h2>
<child-com1 :foo="foo" :boo="boo" :coo="coo" :doo="doo" title="前端工匠"></child-com1>
</div>
</template>
<script>
const childCom1 = () => import("./childCom1.vue");
export default {
components: { childCom1 },
data() {
return {
foo: "Javascript",
boo: "Html",
coo: "CSS",
doo: "Vue"
};
}
};
</script>
//childCom1.vue
<template class="border">
<div>
<p>foo: {{ foo }}</p>
<p>childCom1的$attrs: {{ $attrs }}</p>
<child-com2 v-bind="$attrs"></child-com2>
</div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
components: {
childCom2
},
inheritAttrs: false, // 可以關(guān)閉自動掛載到組件根元素上的沒有在props聲明的屬性
props: {
foo: String // foo作為props屬性綁定
},
created() {
console.log(this.$attrs);
// { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
}
};
</script>
// childCom2.vue
<template>
<div class="border">
<p>boo: {{ boo }}</p>
<p>childCom2: {{ $attrs }}</p>
<child-com3 v-bind="$attrs"></child-com3>
</div>
</template>
<script>
const childCom3 = () => import("./childCom3.vue");
export default {
components: {
childCom3
},
inheritAttrs: false,
props: {
boo: String
},
created() {
console.log(this.$attrs);
// {"coo": "CSS", "doo": "Vue", "title": "前端工匠" }
}
};
</script>
// childCom3.vue
<template>
<div class="border">
<p>childCom3: {{ $attrs }}</p>
</div>
</template>
<script>
export default {
props: {
coo: String,
title: String
}
};
</script>
所示$attrs
表示沒有繼承數(shù)據(jù)的對象坚洽,格式為{屬性名:屬性值}讶舰。Vue2.4提供了$attrs , $listeners
來傳遞數(shù)據(jù)與事件需了,跨級組件之間的通訊變得更簡單援所。
簡單來說:$attrs與$listeners
是兩個對象,$attrs
里存放的是父組件中綁定的非 Props
屬性挪略,$listeners
里存放的是父組件中綁定的非原生事件滔岳。
方法五、provide/inject
1-1 簡介
Vue2.2.0新增API,這對選項需要一起使用摊求,以允許一個祖先組件向其所有子孫后代注入一個依賴刘离,不論組件層次有多深,并在起上下游關(guān)系成立的時間里始終生效茧痕。一言而蔽之:祖先組件中通過provider來提供變量恼除,然后在子孫組件中通過inject來注入變量。
provide / inject API 主要解決了跨級組件間的通信問題令野,不過它的使用場景气破,主要是子組件獲取上級組件的狀態(tài),跨級組件間建立了一種主動提供與依賴注入的關(guān)系狗超。
例:
//a.vue
export default {
provide: {
name: '王者峽谷' //這種綁定是不可響應(yīng)的
}
}
// b.vue
export default {
inject: ['name'],
mounted () {
console.log(this.name) //輸出王者峽谷
}
}
A.vue朴下,我們設(shè)置了一個
provide:name
,值為王者峽谷渗稍,將name這個變量提供給它的所有子組件团滥。
B.vue ,通過 inject 注入了從A組件中提供的name變量拱燃,組件B中力惯,直接通過this.name訪問這個變量了。
這就是 provide / inject API 最核心的用法哮缺。
需要注意的是:provide 和inject綁定并不是可響應(yīng)的尝苇。這是刻意為之的埠胖。然而,如果你傳入了一個可監(jiān)聽的對象诵冒,那么其對象的屬性還是可響應(yīng)的----vue官方文檔,所以谊惭,上面 A.vue 的 name 如果改變了圈盔,B.vue 的 this.name 是不會改變的。
1-2 provide與inject 怎么實現(xiàn)數(shù)據(jù)響應(yīng)式
兩種方法:
1-2-1
- provide祖先組件的實例铁蹈,然后在子孫組件中注入依賴众眨,這樣就可以在子孫組件中直接修改祖先組件的實例的屬性,不過這種方法有個缺點就是這個實例上掛載很多沒有必要的東西比如props沿腰,methods
1-2-2
- 使用2.6最新API Vue.observable 優(yōu)化響應(yīng)式 provide(推薦)
例:
組件D颂龙、E和F獲取A組件傳遞過來的color值纽什,并能實現(xiàn)數(shù)據(jù)響應(yīng)式變化,即A組件的color變化后企巢,組件D让蕾、E、F會跟著變(核心代碼如下:)
[圖片上傳失敗...(image-2af63-1660823023573)]
// A 組件
<div>
<h1>A 組件</h1>
<button @click="() => changeColor()">改變color</button>
<ChildrenB />
<ChildrenC />
</div>
......
data() {
return {
color: "blue"
};
},
// provide() {
// return {
// theme: {
// color: this.color //這種方式綁定的數(shù)據(jù)并不是可響應(yīng)的
// } // 即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";
}
}
}
// 方法二:使用2.6最新API Vue.observable 優(yōu)化響應(yīng)式 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";
// }
// }
// }
// F 組件
<template functional>
<div class="border2">
<h3 :style="{ color: injections.theme.color }">F 組件</h3>
</div>
</template>
<script>
export default {
inject: {
theme: {
//函數(shù)式組件取值不一樣
default: () => ({})
}
}
};
</script>
注:provide 和 inject主要為高階插件/組件庫提供用例,能在業(yè)務(wù)中熟練運用栖疑,可以達到事半功倍的效果!
方法六卿闹、$parent / $children與 ref
-
ref
:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素锻霎;如果用在子組件上,引用就指向組件實例 -
$parent / $children
:訪問父 / 子實例
注意:這兩種都是直接得到組件實例吏口,使用后可以直接調(diào)用組件的方法或訪問數(shù)據(jù)冰更。我們先來看個用 ref來訪問組件的
例:
export default {
data () {
return {
title: 'Vue.js'
}
},
methods: {
sayHello () {
window.alert('Hello');
}
}
}
<template>
<component-a ref="comA"></component-a>
</template>
<script>
export default {
mounted () {
const comA = this.$refs.comA;
console.log(comA.title); // Vue.js
comA.sayHello(); // 彈窗
}
}
</script>
注:這兩種方法的弊端是蜀细,無法在跨級或兄弟間通信。
我們想在 component-a 中深滚,訪問到引用它的頁面中(這里就是 parent.vue)的兩個 component-b 組件涣觉,那這種情況下,就得配置額外的插件或工具了生兆,比如 Vuex 和 Bus 的解決方案膝宁。