Vue2實(shí)現(xiàn)組件通訊的常用方式
前言:
在 Vue 項(xiàng)目開發(fā)中,組件通訊是基礎(chǔ)也是核心。不同的組件關(guān)系(父子、祖先后代、任意組件)有著不同的最優(yōu)解方案。
一、父子通訊
誰(shuí)在模板中引入了另一個(gè)組件,誰(shuí)就是父組件,被引用的就是子組件。父子組件的通訊是最為頻繁的場(chǎng)景。
1、props 配置項(xiàng)
props主要用于讓子組件接收外部(父組件)傳過來的數(shù)據(jù)。它可以實(shí)現(xiàn)“父?jìng)髯?rdquo;,也可以通過傳遞函數(shù)實(shí)現(xiàn)“子傳父”
代碼實(shí)現(xiàn):
父?jìng)髯?/strong>:父組件在子組件標(biāo)簽上通過v-bind即:綁定動(dòng)態(tài)數(shù)據(jù)。
子傳父:父組件先定義一個(gè)帶形參的方法,并將該方法通過屬性傳給子組件;子組件在合適的時(shí)機(jī)調(diào)用該方法,將自身數(shù)據(jù)作為實(shí)參傳入
// 父組件 App.vue
<template>
<div>
<!-- 傳遞屬性 money 和 方法 getSonMsg -->
<Girl :money="money" :getSonMsg="getSonMsg"/>
</div>
</template>
<script>
import Girl from '@/components/Girl';
export default {
components: { Girl },
data() {
return { money: { num: 1000 } }
},
methods: {
getSonMsg(msg) {
console.log('接收到了girl組件傳遞的數(shù)據(jù)', msg); // 父組件在這里接收到了子組件的數(shù)據(jù)
}
}
}
</script>
// 子組件 Girl.vue
<script>
export default {
// 推薦使用對(duì)象方式接收,可限制類型、必要性及默認(rèn)值
props: {
money: { type: Object, required: true },
getSonMsg: { type: Function }
},
data() { return { girlInfo: '我是girl組件' } },
methods: {
sendInfo() {
// 觸發(fā)父組件傳來的方法,并傳入子組件的數(shù)據(jù)實(shí)現(xiàn)子傳父
this.getSonMsg(this.girlInfo);
}
}
}
</script>注意事項(xiàng): props 是只讀的,Vue 底層會(huì)監(jiān)視你對(duì) props 的修改,如果強(qiáng)行修改會(huì)發(fā)出警告。若業(yè)務(wù)需要,請(qǐng)將 props 的內(nèi)容復(fù)制一份到子組件的 data 中再進(jìn)行修改。另外,對(duì)于傳遞的復(fù)雜數(shù)據(jù)類型(如對(duì)象),Vue 無法監(jiān)測(cè)到內(nèi)部屬性內(nèi)容的改變,除非修改了地址值。
2、組件自定義事件
適用于子組件向父組件傳遞數(shù)據(jù)。核心邏輯是:誰(shuí)綁定,誰(shuí)觸發(fā)。父組件給子組件綁定自定義事件,子組件內(nèi)部觸發(fā)該事件
代碼實(shí)現(xiàn)與兩種綁定方式:
方式一:標(biāo)簽上直接綁定 父組件使用 @事件名="回調(diào)函數(shù)" 綁定
// 父組件
<HelloWorld @event2="handleEvent"></HelloWorld>
// 子組件 HelloWorld.vue
methods: {
doSome() {
// 觸發(fā) event2 事件,并可以傳遞多個(gè)參數(shù)
this.$emit('event2', this.msg, 666, 8888);
}
}方式二:代碼動(dòng)態(tài)綁定 (使用 ref) 如果想要更靈活的綁定時(shí)機(jī),可以使用 ref。注意:不能在 created 鉤子中綁定(此時(shí) DOM 未渲染),通常在 mounted 中綁定
// 父組件
<template>
<HelloWorld ref="hello" />
</template>
<script>
export default {
mounted() {
// 使用箭頭函數(shù),確保 this 指向父組件實(shí)例
this.$refs.hello.$on('event2', (msg) => {
console.log('收到數(shù)據(jù)', msg, this);
});
}
}
</script>注意事項(xiàng):
this 指向:如果使用代碼綁定(方式二),回調(diào)函數(shù)一定要寫成箭頭函數(shù)或者配置在父組件的 methods 中。如果寫成普通匿名函數(shù) function() {},此時(shí)的 this 會(huì)指向觸發(fā)事件的子組件(HelloWorld),而不是父組件。
原生事件與解綁:如果要在組件標(biāo)簽上綁定原生的 DOM 事件(如 click),必須加上 .native 修飾符,否則 Vue 會(huì)將其視為自定義事件。組件銷毀前,建議使用 this.\$off('事件名') 或 this.\$off() 解綁事件,防止內(nèi)存泄漏
3、ref / \$parent / \$children
ref:使用在普通 HTML 標(biāo)簽上獲取的是 DOM 元素;使用在組件標(biāo)簽上獲取的是該子組件實(shí)例,可以借此直接調(diào)用子組件的數(shù)據(jù)和方法,如this.\$refs.hello.name
\$parent / \$childre:
子組件通過this.parent.屬性即可訪問父組件;父組件也可以通過this.\$children訪問子組件實(shí)例。
這三者都會(huì)增加組件的強(qiáng)耦合,通常僅作為一種捷徑通信。
二、任意組件通訊
1、 全局事件總線 ($bus)
全局事件總線適用于任何關(guān)系的組件通信(尤其是兄弟組件)。它的原理是給項(xiàng)目中所有的組件找一個(gè)共享的對(duì)象(必須具備 \$on、\$emit、\$off 方法),所有組件都通過它來收發(fā)數(shù)據(jù)。
配置總線的兩種底層方式(明確 vc 與 vm): 要?jiǎng)?chuàng)建一個(gè)所有組件都能訪問的對(duì)象,我們可以利用原型鏈。Vue 的組件實(shí)例(vc - VueComponent)和 Vue 實(shí)例(vm - Vue)都繼承自 Vue 原型。
方式一:創(chuàng)建一個(gè)共享的 vc 對(duì)象
Vue 底層解析組件標(biāo)簽時(shí)會(huì) new VueComponent(),我們可以手動(dòng)創(chuàng)建一個(gè)實(shí)例來作為總線。
// 在 main.js 中
// 1. 獲取 VueComponent 構(gòu)造函數(shù)
const VueComponentConstructor = Vue.extend({});
// 2. 實(shí)例化一個(gè) vc 對(duì)象
const globelvc = new VueComponentConstructor();
// 3. 將其掛載到 Vue 原型上
Vue.prototype.$bus = globelvc;
方式二:直接使用 vm 對(duì)象(推薦寫法)
在 main.js 創(chuàng)建 Vue 根實(shí)例時(shí),在 beforeCreate 鉤子中將當(dāng)前的 this(也就是 vm 實(shí)例自身)掛載到原型上。
// 在 main.js 中
new Vue({
render: (h) => h(App),
beforeCreate() {
// 安裝全局事件總線,$bus 就是當(dāng)前應(yīng)用的 vm
Vue.prototype.$bus = this;
}
}).$mount("#app");使用規(guī)范: 永遠(yuǎn)記?。篈 組件向 B 組件傳數(shù)據(jù),應(yīng)該在 B 組件中綁定事件(接),在 A 組件中觸發(fā)事件(傳)
接收方 (B 組件):mounted() { this.\$bus.\$on('receiveMsg', (msg) => { ... }) }
發(fā)送方 (A 組件):this.\$bus.\$emit('receiveMsg', 數(shù)據(jù))
注意:養(yǎng)成好習(xí)慣,接收方組件被銷毀前,必須在 beforeDestroy 中調(diào)用 this.\$bus.\$off('receiveMsg') 解綁事件
2、消息訂閱與發(fā)布 (pubsub-js)
借助第三方庫(kù)實(shí)現(xiàn),適用于任意前端框架。安裝 npm i pubsub-js 后,引入 pubsub 對(duì)象。接收方使用 pubsub.subscribe('消息名', 回調(diào)) 訂閱,發(fā)送方使用 pubsub.publish('消息名', 數(shù)據(jù)) 發(fā)布
三、Vuex 集中式狀態(tài)管理
當(dāng)多個(gè)組件依賴于同一狀態(tài),或來自不同組件的行為需要變更同一狀態(tài)時(shí),就必須使用 Vuex。 全局事件總線的關(guān)注點(diǎn)在于“數(shù)據(jù)傳來傳去”,并沒有讓數(shù)據(jù)真正共享;而 Vuex 的關(guān)注點(diǎn)在于“數(shù)據(jù)本身就在 Vuex 上共享”,任何組件修改它,其他組件都會(huì)同步更新
1. Vuex 的核心五大屬性
Vuex 包含五個(gè)核心屬性:State、Getters、Mutations、Actions、Modules。
State:存儲(chǔ) Vuex 的基本數(shù)據(jù)(變量)。
Getters:基于 State 的派生數(shù)據(jù),邏輯復(fù)雜且復(fù)用性高時(shí)使用,相當(dāng)于 State 的計(jì)算屬性。
Mutations:提交更新數(shù)據(jù)的唯一途徑且必須是同步的。包含事件類型和回調(diào)函數(shù),回調(diào)接收 state 和傳入的值作為參數(shù),使用 this.\$store.commit() 觸發(fā)。
Actions:處理復(fù)雜的業(yè)務(wù)邏輯或包含任意異步操作(如發(fā)送 ajax 請(qǐng)求)。Action 拿到結(jié)果后提交給 Mutation 進(jìn)行狀態(tài)變更,而不是直接修改 State。使用 this.\$store.dispatch() 觸發(fā)。
Modules:如果將所有狀態(tài)集中在一棵樹上會(huì)非常臃腫。Modules 允許將 Store 分割成包含自己 State、Mutations、Actions 的多個(gè)模塊,方便管理。
實(shí)現(xiàn):我們將這四個(gè)屬性集中在 Vuex 的核心對(duì)象配置中(通常在 store/index.js 或類似文件中)
import Vue from "vue";
import Vuex from "vuex";
import axios from "axios"; // 引入網(wǎng)絡(luò)請(qǐng)求庫(kù)
Vue.use(Vuex);
// 1. State:定義全局共享的數(shù)據(jù)
const state = {
username: "zhangsan", // 基礎(chǔ)字符串狀態(tài)
studentName: [
{ id: "001", name: "孫悟空" },
{ id: "002", name: "豬八戒" }
],
userList: [] // 準(zhǔn)備存放異步請(qǐng)求來的列表數(shù)據(jù)
};
// 2. Getters:基于 State 加工的計(jì)算屬性
const getters = {
// 每一個(gè) getter 方法會(huì)自動(dòng)接收 state 作為第一個(gè)參數(shù)
// 需求:將 username 反轉(zhuǎn)展示
reverseName(state) {
return state.username.split("").reverse().join("");
}
};
// 3. Mutations:負(fù)責(zé)同步更新 State 的方法
const mutations = {
// 接收 state 作為第一個(gè)參數(shù),value(提交的載荷)作為第二個(gè)參數(shù)
SAVE_STUDENT(state, value) {
// 只能在此進(jìn)行同步操作直接修改 state
state.studentName.unshift({ id: Date.now(), name: value });
},
// 用于接收異步操作拿回來的數(shù)據(jù)并賦值給 state
GET_USER_INFO(state, value) {
state.userList = value;
}
};
// 4. Actions:處理異步操作和復(fù)雜邏輯
const actions = {
// context 相當(dāng)于一個(gè)迷你的 store 對(duì)象,包含 commit、dispatch 等方法
// 示例 A:簡(jiǎn)單的封裝,將外部傳來的值交給 Mutation
saveStudent(context, value) {
// 可以在這里做一些邏輯判斷,判斷通過后再提交
context.commit("SAVE_STUDENT", value);
},
// 示例 B:真實(shí)的異步 Ajax 請(qǐng)求
async getUserInfo(context) {
try {
// 模擬發(fā)送異步網(wǎng)絡(luò)請(qǐng)求獲取數(shù)據(jù)
let res = await axios.get("api/user");
// 拿到數(shù)據(jù)后,提交給 Mutations 去更新 State
context.commit("GET_USER_INFO", res.data);
} catch (error) {
console.log(error.message);
}
}
};
// 創(chuàng)建并暴露 store 對(duì)象
export default new Vuex.Store({
state,
getters,
mutations,
actions,
});2. Vuex 模塊化開發(fā)與 map 輔助函數(shù)
在大型項(xiàng)目中,模塊化開發(fā)是必選項(xiàng)。開啟命名空間 namespaced: true 可以讓各個(gè)模塊的狀態(tài)內(nèi)聚。 同時(shí),使用 mapState, mapGetters, mapActions, mapMutations 能夠大幅簡(jiǎn)化組件內(nèi)部調(diào)用 store 的代碼。
模塊化與輔助函數(shù)代碼實(shí)踐:
// store/student.js (學(xué)生模塊)
export default {
namespaced: true, // 開啟命名空間
state: { studentNum: 5, studentName: "張三" },
getters: {
reverseStudentName(state) { return state.studentName.split("").reverse().join(""); }
},
mutations: {
STU_NUM_FUN(state, value) { state.studentNum += value; }
},
actions: {
stuNumFun(context, value) { context.commit("STU_NUM_FUN", value); }
}
};
// store/index.js (主文件)
import Vue from "vue";
import Vuex from "vuex";
import student from "./student";
Vue.use(Vuex);
export default new Vuex.Store({
modules: { student } // 注冊(cè)模塊
});
在組件中使用輔助函數(shù)映射:
// 組件內(nèi)
import { mapState, mapGetters, mapActions } from 'vuex';
export default {
computed: {
// 數(shù)組形式或?qū)ο笮问接成淠K內(nèi)的 state 和 getters
...mapState('student', ['studentNum', 'studentName']),
...mapGetters('student', ['reverseStudentName'])
},
methods: {
// 映射 actions,模板中調(diào)用時(shí)直接傳參 @click="stuNumFun(2)"
...mapActions('student', ['stuNumFun'])
}
}
3. Action 中的異步請(qǐng)求
如果 ajax 請(qǐng)求來的數(shù)據(jù)需要被多個(gè)組件共享復(fù)用,應(yīng)該將請(qǐng)求邏輯封裝在 Vuex 的 Action 里,并包裝成 Promise。
// test.js 模塊
import axios from "axios";
export default {
namespaced: true,
state: { list: [] },
mutations: { GET_USER_INFO(state, value) { state.list = value; } },
actions: {
async getUserInfo(context) {
try {
let res = await axios.get("api/user");
context.commit("GET_USER_INFO", res.data); // 請(qǐng)求成功后提交 mutation
} catch (error) { console.log(error.message); }
}
}
}
4. Vuex 數(shù)據(jù)刷新丟失問題與持久化
痛點(diǎn):F5 刷新頁(yè)面時(shí),Vue 實(shí)例會(huì)被銷毀重建,Vuex 寫在內(nèi)存中的 state 數(shù)據(jù)會(huì)重新初始化,導(dǎo)致登錄 token 等數(shù)據(jù)丟失。 解決方案:引入 vuex-persistedstate 插件進(jìn)行數(shù)據(jù)持久化,它會(huì)自動(dòng)將數(shù)據(jù)同步到 localStorage 中。
// 安裝: npm install vuex-persistedstate
// store/index.js
import createPersistedState from 'vuex-persistedstate'
export const store = new Vuex.Store({
modules: { student },
plugins: [
createPersistedState({
// 可選配置:指定需要持久化的模塊數(shù)據(jù)
reducer(val) {
return { student: val.student }
}
})
]
})
四、其他通訊方式
1. \$attrs 與 \$listeners (爺孫透?jìng)?
用于祖先組件向深層后代組件傳遞數(shù)據(jù),解決中間組件僅起到橋梁作用的問題。
\$attrs 包含了祖先傳遞但在當(dāng)前組件 props 中未聲明的屬性。在中間組件設(shè)置 inheritAttrs: false 阻止屬性自動(dòng)綁定到根元素,然后通過 v-bind="$attrs" 將其向下透?jìng)鳌?/p>
\$listeners 包含祖先傳遞的所有事件監(jiān)聽器,通過 v-on="$listeners" 向下傳遞,孫組件即可直接觸發(fā)爺爺組件的事件
2. Provide 與 Inject (依賴注入)
同樣用于祖先向后代傳遞。祖先組件通過 provide() 返回提供的數(shù)據(jù),后代通過 inject: ['屬性名'] 接收。 注意:這主要用于單向傳遞。如果祖先傳遞的是基礎(chǔ)類型,后代修改它會(huì)報(bào)錯(cuò);如果是復(fù)雜引用類型,修改雖不報(bào)錯(cuò)但會(huì)失去響應(yīng)式,破壞單向數(shù)據(jù)流結(jié)構(gòu),增加代碼維護(hù)難度。
3. v-model 通信
在自定義組件上,v-model 本質(zhì)上是 :value 和 @input 事件的語(yǔ)法糖,用于實(shí)現(xiàn)極其方便的父子雙向綁定。父組件直接 <Child v-model="data"/>,子組件接收 props: ['value'],數(shù)據(jù)變化時(shí)
this.$ emit('input', 新值) 即可實(shí)現(xiàn)同步修改源數(shù)據(jù)。
4. 插槽 Slots (結(jié)構(gòu)傳遞)
插槽讓父組件可以向子組件指定位置插入完整的 HTML 結(jié)構(gòu)。
默認(rèn)/具名插槽:父組件向子組件分發(fā)結(jié)構(gòu)。
作用域插槽(子傳父的變體):數(shù)據(jù)在子組件自身,但根據(jù)數(shù)據(jù)生成的 HTML 結(jié)構(gòu)由父組件決定。子組件通過 <slot :自定義名="數(shù)據(jù)"> 傳出數(shù)據(jù),父組件用 <template scope="接收名"> 拿到數(shù)據(jù)并構(gòu)建 UI。
總覽
| 組件關(guān)系 | 傳遞方式 |
| 父?jìng)髯?/td> | props,v-model,默認(rèn)插槽,具名插槽, \$parent |
| 子傳父 | props,自定義事件,v-model, \$refs, \$children,作用域插槽 |
| 祖先與后代 | \$attrs / \$listeners ,provide / inject |
| 任意組件通訊 | \$bus,pubsub,vuex |
以上就是Vue2實(shí)現(xiàn)組件通訊的常用方式的詳細(xì)內(nèi)容,更多關(guān)于Vue2組件通訊方式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue-router?導(dǎo)航完成后獲取數(shù)據(jù)的實(shí)現(xiàn)方法
這篇文章主要介紹了vue-router?導(dǎo)航完成后獲取數(shù)據(jù),通過使用生命周期的 created() 函數(shù),在組件創(chuàng)建完成后調(diào)用該方法,本文結(jié)合實(shí)例代碼給大家講解的非常詳細(xì)需要的朋友可以參考下2022-11-11
使用Vue3和ApexCharts實(shí)現(xiàn)交互式3D折線圖
ApexCharts 是一個(gè)功能強(qiáng)大的 JavaScript 庫(kù),用于創(chuàng)建交互式、可定制的圖表,在 Vue.js 中,它可以通過 vue3-apexcharts 插件輕松集成,本文給大家介紹了使用Vue3和ApexCharts實(shí)現(xiàn)交互式3D折線圖,需要的朋友可以參考下2024-06-06
Vue項(xiàng)目之學(xué)生管理系統(tǒng)實(shí)例詳解
這篇文章主要為大家詳細(xì)介紹了Vue項(xiàng)目之學(xué)生管理系統(tǒng)實(shí)例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03
vue3?ts編寫echart是tooltip無法展示的解決
這篇文章主要介紹了vue3?ts編寫echart是tooltip無法展示的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
Vue簡(jiǎn)易注冊(cè)頁(yè)面+發(fā)送驗(yàn)證碼功能的實(shí)現(xiàn)示例
本文主要介紹了Vue簡(jiǎn)易注冊(cè)頁(yè)面+發(fā)送驗(yàn)證碼功能的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
vue?select組件綁定的值為數(shù)字類型遇到的問題
這篇文章主要介紹了vue?select組件綁定的值為數(shù)字類型遇到的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09

