Vue中跨頁面通信的8種主流方式詳解
在Vue項(xiàng)目開發(fā)中,跨頁面通信(非父子組件、不同路由頁面間)是高頻需求,比如“列表頁跳轉(zhuǎn)詳情頁傳遞數(shù)據(jù)”“頁面A操作后同步更新頁面B內(nèi)容”。本文整理8種主流通信方式,按“常用度+實(shí)用性”排序,每種方式附完整可運(yùn)行Demo、適配場(chǎng)景和注意事項(xiàng),覆蓋Vue2、Vue3所有項(xiàng)目場(chǎng)景,新手也能直接復(fù)制落地。
一、路由參數(shù)傳遞(最常用,簡單場(chǎng)景首選)
核心思路:通過路由跳轉(zhuǎn)時(shí)攜帶參數(shù),目標(biāo)頁面接收參數(shù),適合“頁面跳轉(zhuǎn)傳值”場(chǎng)景(如列表頁→詳情頁),分為「query參數(shù)」和「params參數(shù)」兩種,按需選擇。
query參數(shù)(路徑可見,適合簡單數(shù)據(jù))- 完整可運(yùn)行Demo
適配場(chǎng)景:列表頁跳轉(zhuǎn)詳情頁,傳遞簡單ID、名稱等數(shù)據(jù),刷新頁面不丟失。
前置準(zhǔn)備:已配置Vue Router(Vue2/Vue3均可,以下Demo分別提供兩種版本)。
Vue3 Demo(組合式API)
// 1. 路由配置(router/index.js)
import { createRouter, createWebHistory } from 'vue-router';
import PageA from '@/views/PageA.vue';
import PageB from '@/views/PageB.vue';
const routes = [
{ path: '/pageA', name: 'PageA', component: PageA },
{ path: '/pageB', name: 'PageB', component: PageB }
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;
// 2. 頁面A(跳轉(zhuǎn)方,@/views/PageA.vue)
<template>
<div class="page-a">
<h3>頁面A(query參數(shù)跳轉(zhuǎn))</h3>
<!-- 方式1:router-link跳轉(zhuǎn) -->
<router-link
:to="{ path: '/pageB', query: { id: 123, name: 'Vue跨頁面通信Demo' } }"
class="btn"
>
點(diǎn)擊跳轉(zhuǎn)頁面B(router-link)
</router-link>
<!-- 方式2:編程式導(dǎo)航跳轉(zhuǎn) -->
<button @click="goToPageB" class="btn">點(diǎn)擊跳轉(zhuǎn)頁面B(編程式)</button>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router';
// 編程式導(dǎo)航
const router = useRouter();
const goToPageB = () => {
router.push({
path: '/pageB',
query: { id: 123, name: 'Vue跨頁面通信Demo' }
});
};
</script>
<style scoped>
.btn { margin: 0 10px; padding: 6px 12px; cursor: pointer; }
</style>
// 3. 頁面B(接收方,@/views/PageB.vue)
<template>
<div class="page-b">
<h3>頁面B(接收query參數(shù))</h3>
<div>接收的ID:{{ id }}</div>
<div>接收的名稱:{{ name }}</div>
<!-- 返回頁面A -->
<router-link to="/pageA" class="btn">返回頁面A</router-link>
</div>
</template>
<script setup>
import { useRoute } from 'vue-router';
import { ref } from 'vue';
const route = useRoute();
// 接收參數(shù)(query參數(shù)默認(rèn)是字符串,需手動(dòng)轉(zhuǎn)類型)
const id = ref(Number(route.query.id));
const name = ref(route.query.name);
// 監(jiān)聽路由變化(若頁面不刷新,參數(shù)變化時(shí)同步更新)
watch(
() => route.query,
(newQuery) => {
id.value = Number(newQuery.id);
name.value = newQuery.name;
},
{ immediate: true }
);
</script>
// 4. main.js(入口文件)
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
createApp(App).use(router).mount('#app');Vue2 Demo(選項(xiàng)式API)
// 1. 路由配置(router/index.js)
import Vue from 'vue';
import Router from 'vue-router';
import PageA from '@/views/PageA';
import PageB from '@/views/PageB';
Vue.use(Router);
export default new Router({
routes: [
{ path: '/pageA', name: 'PageA', component: PageA },
{ path: '/pageB', name: 'PageB', component: PageB }
]
});
// 2. 頁面A(跳轉(zhuǎn)方,@/views/PageA.vue)
<template>
<div class="page-a">
<h3>頁面A(query參數(shù)跳轉(zhuǎn))</h3>
<router-link
:to="{ path: '/pageB', query: { id: 123, name: 'Vue跨頁面通信Demo' } }"
class="btn"
>
點(diǎn)擊跳轉(zhuǎn)頁面B(router-link)
</router-link>
<button @click="goToPageB" class="btn">點(diǎn)擊跳轉(zhuǎn)頁面B(編程式)</button>
</div>
</template>
<script>
export default {
methods: {
goToPageB() {
this.$router.push({
path: '/pageB',
query: { id: 123, name: 'Vue跨頁面通信Demo' }
});
}
}
};
</script>
// 3. 頁面B(接收方,@/views/PageB.vue)
<template>
<div class="page-b">
<h3>頁面B(接收query參數(shù))</h3>
<div>接收的ID:{{ id }}</div>
<div>接收的名稱:{{ name }}</div>
<router-link to="/pageA" class="btn">返回頁面A</router-link>
</div>
</template>
<script>
export default {
data() {
return {
id: '',
name: ''
};
},
mounted() {
// 初始接收參數(shù)
this.id = Number(this.$route.query.id);
this.name = this.$route.query.name;
},
watch: {
// 監(jiān)聽路由變化,同步更新參數(shù)
'$route.query': {
handler(newQuery) {
this.id = Number(newQuery.id);
this.name = newQuery.name;
},
immediate: true
}
}
};
</script>
// 4. main.js(入口文件)
import Vue from 'vue';
import App from './App';
import router from './router';
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
});
params參數(shù)(路徑不可見,適合敏感/復(fù)雜數(shù)據(jù))- 完整可運(yùn)行Demo
適配場(chǎng)景:傳遞敏感數(shù)據(jù)(如用戶隱私信息),路徑不可見,需路由配置占位符避免刷新丟失。
Vue3 Demo(組合式API)
// 1. 路由配置(router/index.js,必須添加params占位符)
import { createRouter, createWebHistory } from 'vue-router';
import PageA from '@/views/PageA.vue';
import PageB from '@/views/PageB.vue';
const routes = [
{ path: '/pageA', name: 'PageA', component: PageA },
// 配置params占位符::id、:name(與傳遞的參數(shù)名一致)
{ path: '/pageB/:id/:name', name: 'PageB', component: PageB }
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;
// 2. 頁面A(跳轉(zhuǎn)方,@/views/PageA.vue)
<template>
<div class="page-a">
<h3>頁面A(params參數(shù)跳轉(zhuǎn))</h3>
<!-- 注意:params參數(shù)必須用name跳轉(zhuǎn),不能用path -->
<button @click="goToPageB" class="btn">點(diǎn)擊跳轉(zhuǎn)頁面B</button>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router';
const router = useRouter();
const goToPageB = () => {
router.push({
name: 'PageB', // 必須用name
params: { id: 456, name: '敏感數(shù)據(jù)Demo' } // 傳遞params參數(shù)
});
};
</script>
// 3. 頁面B(接收方,@/views/PageB.vue)
<template>
<div class="page-b">
<h3>頁面B(接收params參數(shù))</h3>
<div>接收的ID:{{ id }}</div>
<div>接收的敏感名稱:{{ name }}</div>
<router-link to="/pageA" class="btn">返回頁面A</router-link>
</div>
</template>
<script setup>
import { useRoute } from 'vue-router';
import { ref, watch } from 'vue';
const route = useRoute();
const id = ref(Number(route.params.id));
const name = ref(route.params.name);
// 監(jiān)聽路由變化,同步更新參數(shù)
watch(
() => route.params,
(newParams) => {
id.value = Number(newParams.id);
name.value = newParams.name;
},
{ immediate: true }
);
</script>
Vue2 Demo(選項(xiàng)式API)
// 1. 路由配置(router/index.js)
import Vue from 'vue';
import Router from 'vue-router';
import PageA from '@/views/PageA';
import PageB from '@/views/PageB';
Vue.use(Router);
export default new Router({
routes: [
{ path: '/pageA', name: 'PageA', component: PageA },
{ path: '/pageB/:id/:name', name: 'PageB', component: PageB }
]
});
// 2. 頁面A(跳轉(zhuǎn)方,@/views/PageA.vue)
<template>
<div class="page-a">
<h3>頁面A(params參數(shù)跳轉(zhuǎn))</h3>
<button @click="goToPageB" class="btn">點(diǎn)擊跳轉(zhuǎn)頁面B</button>
</div>
</template>
<script>
export default {
methods: {
goToPageB() {
this.$router.push({
name: 'PageB',
params: { id: 456, name: '敏感數(shù)據(jù)Demo' }
});
}
}
};
</script>
// 3. 頁面B(接收方,@/views/PageB.vue)
<template>
<div class="page-b">
<h3>頁面B(接收params參數(shù))</h3>
<div>接收的ID:{{ id }}</div>
<div>接收的敏感名稱:{{ name }}</div>
<router-link to="/pageA" class="btn">返回頁面A</router-link>
</div>
</template>
<script>
export default {
data() {
return {
id: '',
name: ''
};
},
mounted() {
this.id = Number(this.$route.params.id);
this.name = this.$route.params.name;
},
watch: {
'$route.params': {
handler(newParams) {
this.id = Number(newParams.id);
this.name = newParams.name;
},
immediate: true
}
}
};
</script>
注意事項(xiàng)
- query參數(shù):路徑可見(如/pageB?id=123),刷新頁面不丟失,適合傳遞簡單數(shù)據(jù)(字符串、數(shù)字);
- params參數(shù):路徑不可見,刷新頁面會(huì)丟失(除非路由配置中寫占位符),適合傳遞敏感、臨時(shí)數(shù)據(jù);
- 傳遞復(fù)雜數(shù)據(jù)(如對(duì)象)時(shí),需先JSON.stringify轉(zhuǎn)字符串,接收時(shí)再JSON.parse解析(避免數(shù)據(jù)錯(cuò)亂)。
二、Vuex/Pinia(全局狀態(tài)管理,復(fù)雜場(chǎng)景首選)
核心思路:通過全局狀態(tài)倉庫存儲(chǔ)數(shù)據(jù),所有頁面均可讀寫,適合“多頁面共享數(shù)據(jù)”場(chǎng)景(如用戶信息、全局配置、多頁面聯(lián)動(dòng)),Vue2常用Vuex,Vue3首選Pinia(更輕量、簡潔)。
Pinia(Vue3首選)- 完整可運(yùn)行Demo
適配場(chǎng)景:多頁面共享用戶信息、全局計(jì)數(shù)器等,支持跨頁面實(shí)時(shí)聯(lián)動(dòng),無需手動(dòng)傳遞參數(shù)。
前置準(zhǔn)備:安裝Pinia依賴(npm install pinia)。
// 1. 創(chuàng)建Pinia實(shí)例并注冊(cè)(main.js)
import { createApp } from 'vue';
import App from './App.vue';
import { createPinia } from 'pinia'; // 引入Pinia
import router from './router';
const app = createApp(App);
app.use(createPinia()); // 注冊(cè)Pinia
app.use(router);
app.mount('#app');
// 2. 創(chuàng)建全局倉庫(store/modules/user.js)
import { defineStore } from 'pinia';
// 定義倉庫(user為倉庫唯一標(biāo)識(shí))
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: { id: 1, name: '測(cè)試用戶', age: 20 }, // 全局共享數(shù)據(jù)
count: 0 // 全局計(jì)數(shù)器(用于跨頁面聯(lián)動(dòng))
}),
actions: {
// 修改用戶信息
setUserInfo(info) {
this.userInfo = info;
},
// 增加計(jì)數(shù)器
addCount() {
this.count++;
},
// 重置計(jì)數(shù)器
resetCount() {
this.count = 0;
}
}
});
// 3. 頁面A(修改全局?jǐn)?shù)據(jù),@/views/PageA.vue)
<template>
<div class="page-a">
<h3>頁面A(修改Pinia全局?jǐn)?shù)據(jù))</h3>
<div>當(dāng)前全局計(jì)數(shù)器:{{ userStore.count }}</div>
<button @click="userStore.addCount" class="btn">計(jì)數(shù)器+1</button>
<button @click="updateUserInfo" class="btn">修改用戶信息</button>
<router-link to="/pageB" class="btn">跳轉(zhuǎn)到頁面B查看數(shù)據(jù)</router-link>
</div>
</template>
<script setup>
import { useUserStore } from '@/store/modules/user';
// 引入并使用倉庫
const userStore = useUserStore();
// 修改用戶信息
const updateUserInfo = () => {
userStore.setUserInfo({
id: 2,
name: '新用戶',
age: 22
});
};
</script>
// 4. 頁面B(讀取/修改全局?jǐn)?shù)據(jù),@/views/PageB.vue)
<template>
<div class="page-b">
<h3>頁面B(讀取Pinia全局?jǐn)?shù)據(jù))</h3>
<div>用戶ID:{{ userStore.userInfo.id }}</div>
<div>用戶名:{{ userStore.userInfo.name }}</div>
<div>當(dāng)前全局計(jì)數(shù)器:{{ userStore.count }}</div>
<button @click="userStore.addCount" class="btn">計(jì)數(shù)器+1</button>
<button @click="userStore.resetCount" class="btn">重置計(jì)數(shù)器</button>
<router-link to="/pageA" class="btn">返回頁面A</router-link>
</div>
</template>
<script setup>
import { useUserStore } from '@/store/modules/user';
const userStore = useUserStore();
</script>
Vuex(Vue2常用)- 完整可運(yùn)行Demo
前置準(zhǔn)備:安裝Vuex依賴(npm install vuex@3,Vue2對(duì)應(yīng)Vuex3版本)。
// 1. 創(chuàng)建Vuex倉庫并注冊(cè)(main.js)
import Vue from 'vue';
import App from './App';
import router from './router';
import Vuex from 'vuex'; // 引入Vuex
import store from './store'; // 引入倉庫
Vue.use(Vuex); // 注冊(cè)Vuex
new Vue({
el: '#app',
router,
store, // 注入倉庫
components: { App },
template: '<App/>'
});
// 2. 創(chuàng)建全局倉庫(store/index.js)
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
userInfo: { id: 1, name: '測(cè)試用戶', age: 20 },
count: 0
},
mutations: {
// 同步修改狀態(tài)(必須通過mutation修改state)
setUserInfo(state, info) {
state.userInfo = info;
},
addCount(state) {
state.count++;
},
resetCount(state) {
state.count = 0;
}
},
actions: {
// 異步修改狀態(tài)(如接口請(qǐng)求后修改)
addCountAsync({ commit }) {
setTimeout(() => {
commit('addCount'); // 調(diào)用mutation修改state
}, 1000);
}
},
getters: {
// 計(jì)算屬性(簡化數(shù)據(jù)讀?。?
userName: state => state.userInfo.name
}
});
// 3. 頁面A(修改全局?jǐn)?shù)據(jù),@/views/PageA.vue)
<template>
<div class="page-a">
<h3>頁面A(修改Vuex全局?jǐn)?shù)據(jù))</h3>
<div>當(dāng)前全局計(jì)數(shù)器:{{ $store.state.count }}</div>
<div>用戶名:{{ $store.getters.userName }}</div>
<button @click="$store.commit('addCount')" class="btn">計(jì)數(shù)器+1(同步)</button>
<button @click="$store.dispatch('addCountAsync')" class="btn">計(jì)數(shù)器+1(異步)</button>
<button @click="updateUserInfo" class="btn">修改用戶信息</button>
<router-link to="/pageB" class="btn">跳轉(zhuǎn)到頁面B查看數(shù)據(jù)</router-link>
</div>
</template>
<script>
export default {
methods: {
updateUserInfo() {
// 調(diào)用mutation修改用戶信息
this.$store.commit('setUserInfo', {
id: 2,
name: '新用戶',
age: 22
});
}
}
};
</script>
// 4. 頁面B(讀取/修改全局?jǐn)?shù)據(jù),@/views/PageB.vue)
<template>
<div class="page-b">
<h3>頁面B(讀取Vuex全局?jǐn)?shù)據(jù))</h3>
<div>用戶ID:{{ $store.state.userInfo.id }}</div>
<div>用戶名:{{ $store.getters.userName }}</div>
<div>當(dāng)前全局計(jì)數(shù)器:{{ $store.state.count }}</div>
<button @click="$store.commit('addCount')" class="btn">計(jì)數(shù)器+1</button>
<button @click="$store.commit('resetCount')" class="btn">重置計(jì)數(shù)器</button>
<router-link to="/pageA" class="btn">返回頁面A</router-link>
</div>
</template>
<script>
export default {};
</script>
注意事項(xiàng)
- Pinia無需手動(dòng)注冊(cè),Vuex需在main.js中注冊(cè)(Vue2:Vue.use(Vuex);Vue3:app.use(store));
- 全局狀態(tài)會(huì)在頁面刷新后丟失,需配合localStorage/sessionStorage緩存(下文會(huì)講);
- 適合頻繁共享、多頁面聯(lián)動(dòng)的數(shù)據(jù),不適合臨時(shí)跳轉(zhuǎn)傳值(沒必要)。
三、localStorage/sessionStorage(本地存儲(chǔ),持久化傳值)
核心思路:利用瀏覽器本地存儲(chǔ)API,將數(shù)據(jù)存入本地,所有頁面均可讀取,適合“持久化數(shù)據(jù)”場(chǎng)景(如用戶登錄狀態(tài)、記住密碼、長期保存的配置),分為兩種存儲(chǔ)方式,按需選擇。
完整可運(yùn)行Demo(Vue2/Vue3通用)
適配場(chǎng)景:持久化存儲(chǔ)用戶登錄狀態(tài)、記住密碼,刷新頁面不丟失,跨頁面共享。
// 1. 封裝本地存儲(chǔ)工具(utils/storage.js,簡化版,通用)
// 存儲(chǔ)localStorage(永久存儲(chǔ))
export const setLocal = (key, value) => {
// 處理對(duì)象/數(shù)組,轉(zhuǎn)為字符串
const val = typeof value === 'object' ? JSON.stringify(value) : value;
localStorage.setItem(key, val);
};
// 獲取localStorage
export const getLocal = (key) => {
const val = localStorage.getItem(key);
try {
// 嘗試解析為對(duì)象/數(shù)組
return JSON.parse(val);
} catch (e) {
// 解析失敗,返回原始字符串
return val;
}
};
// 刪除localStorage
export const removeLocal = (key) => {
localStorage.removeItem(key);
};
// 存儲(chǔ)sessionStorage(會(huì)話存儲(chǔ))
export const setSession = (key, value) => {
const val = typeof value === 'object' ? JSON.stringify(value) : value;
sessionStorage.setItem(key, val);
};
// 獲取sessionStorage
export const getSession = (key) => {
const val = sessionStorage.getItem(key);
try {
return JSON.parse(val);
} catch (e) {
return val;
}
};
// 2. 頁面A(存儲(chǔ)數(shù)據(jù),@/views/PageA.vue,Vue3示例,Vue2用法類似)
<template>
<div class="page-a">
<h3>頁面A(存儲(chǔ)本地?cái)?shù)據(jù))</h3>
<button @click="saveLocalData" class="btn">存儲(chǔ)永久數(shù)據(jù)(localStorage)</button>
<button @click="saveSessionData" class="btn">存儲(chǔ)臨時(shí)數(shù)據(jù)(sessionStorage)</button>
<button @click="removeLocal('userInfo')" class="btn">刪除永久數(shù)據(jù)</button>
<router-link to="/pageB" class="btn">跳轉(zhuǎn)到頁面B讀取數(shù)據(jù)</router-link>
</div>
</template>
<script setup>
import { setLocal, setSession, removeLocal } from '@/utils/storage';
// 存儲(chǔ)永久數(shù)據(jù)(刷新頁面不丟失,除非手動(dòng)刪除)
const saveLocalData = () => {
setLocal('userInfo', { id: 1, name: '測(cè)試用戶', remember: true });
alert('永久數(shù)據(jù)存儲(chǔ)成功!');
};
// 存儲(chǔ)臨時(shí)數(shù)據(jù)(關(guān)閉標(biāo)簽頁/瀏覽器后丟失)
const saveSessionData = () => {
setSession('tempData', '臨時(shí)測(cè)試數(shù)據(jù)123');
alert('臨時(shí)數(shù)據(jù)存儲(chǔ)成功!');
};
</script>
// 3. 頁面B(讀取數(shù)據(jù),@/views/PageB.vue,Vue3示例)
<template>
<div class="page-b">
<h3>頁面B(讀取本地?cái)?shù)據(jù))</h3>
<div>永久數(shù)據(jù)(localStorage):{{ userInfo }}</div>
<div>臨時(shí)數(shù)據(jù)(sessionStorage):{{ tempData }}</div>
<router-link to="/pageA" class="btn">返回頁面A</router-link>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { getLocal, getSession } from '@/utils/storage';
const userInfo = ref({});
const tempData = ref('');
// 頁面掛載時(shí)讀取數(shù)據(jù)
onMounted(() => {
userInfo.value = getLocal('userInfo') || {};
tempData.value = getSession('tempData') || '暫無臨時(shí)數(shù)據(jù)';
});
</script>
// Vue2 頁面B示例(@/views/PageB.vue)
<template>
<div class="page-b">
<h3>頁面B(讀取本地?cái)?shù)據(jù))</h3>
<div>永久數(shù)據(jù)(localStorage):{{ userInfo }}</div>
<div>臨時(shí)數(shù)據(jù)(sessionStorage):{{ tempData }}</div>
<router-link to="/pageA" class="btn">返回頁面A</router-link>
</div>
</template>
<script>
import { getLocal, getSession } from '@/utils/storage';
export default {
data() {
return {
userInfo: {},
tempData: ''
};
},
mounted() {
this.userInfo = getLocal('userInfo') || {};
this.tempData = getSession('tempData') || '暫無臨時(shí)數(shù)據(jù)';
}
};
</script>
注意事項(xiàng)
- 本地存儲(chǔ)只能存儲(chǔ)字符串,傳遞對(duì)象、數(shù)組時(shí),需用JSON.stringify轉(zhuǎn)字符串,接收時(shí)用JSON.parse解析;
- localStorage容量約5MB,不可存儲(chǔ)過大數(shù)據(jù),且數(shù)據(jù)暴露在本地,不適合存儲(chǔ)敏感數(shù)據(jù)(如token,建議加密后存儲(chǔ));
- 數(shù)據(jù)變化時(shí),頁面不會(huì)自動(dòng)響應(yīng),需手動(dòng)監(jiān)聽(下文“監(jiān)聽本地存儲(chǔ)”會(huì)補(bǔ)充)。
四、EventBus(事件總線,簡單跨頁面通信)
核心思路:創(chuàng)建一個(gè)全局事件總線,頁面A觸發(fā)事件并傳遞數(shù)據(jù),頁面B監(jiān)聽事件并接收數(shù)據(jù),適合“簡單跨頁面聯(lián)動(dòng)”場(chǎng)景(如頁面A操作后,頁面B同步更新),Vue2和Vue3實(shí)現(xiàn)方式略有差異。
Vue3 實(shí)現(xiàn) - 完整可運(yùn)行Demo
適配場(chǎng)景:頁面A修改數(shù)據(jù)后,頁面B實(shí)時(shí)更新,無需跳轉(zhuǎn)路由(如兩個(gè)標(biāo)簽頁聯(lián)動(dòng))。
// 1. 創(chuàng)建事件總線(utils/eventBus.js)
import { ref } from 'vue';
// 定義總線容器
const bus = ref({});
// 觸發(fā)事件(發(fā)送數(shù)據(jù))
export const emitEvent = (eventName, data) => {
// 若有監(jiān)聽該事件的回調(diào),執(zhí)行并傳遞數(shù)據(jù)
bus.value[eventName]?.(data);
};
// 監(jiān)聽事件(接收數(shù)據(jù))
export const onEvent = (eventName, callback) => {
// 注冊(cè)回調(diào)函數(shù)
bus.value[eventName] = callback;
};
// 取消監(jiān)聽(避免內(nèi)存泄漏)
export const offEvent = (eventName) => {
// 刪除事件回調(diào)
delete bus.value[eventName];
};
// 2. 頁面A(觸發(fā)事件,發(fā)送數(shù)據(jù),@/views/PageA.vue)
<template>
<div class="page-a">
<h3>頁面A(EventBus發(fā)送數(shù)據(jù))</h3>
<input v-model="message" placeholder="請(qǐng)輸入要傳遞的內(nèi)容" />
<button @click="sendData" class="btn">發(fā)送數(shù)據(jù)到頁面B</button>
<router-link to="/pageB" class="btn">跳轉(zhuǎn)到頁面B</router-link>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { emitEvent } from '@/utils/eventBus';
const message = ref('');
// 觸發(fā)事件,傳遞數(shù)據(jù)
const sendData = () => {
if (!message.value) {
alert('請(qǐng)輸入內(nèi)容');
return;
}
emitEvent('sendData', { message: message.value, time: new Date().toLocaleString() });
message.value = '';
alert('數(shù)據(jù)發(fā)送成功!');
};
</script>
// 3. 頁面B(監(jiān)聽事件,接收數(shù)據(jù),@/views/PageB.vue)
<template>
<div class="page-b">
<h3>頁面B(EventBus接收數(shù)據(jù))</h3>
<div class="data-list">
<div v-for="(item, index) in dataList" :key="index">
{{ item.time }}:{{ item.message }}
</div>
<div v-if="dataList.length === 0">暫無數(shù)據(jù)</div>
</div>
<router-link to="/pageA" class="btn">返回頁面A</router-link>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { onEvent, offEvent } from '@/utils/eventBus';
const dataList = ref([]);
onMounted(() => {
// 監(jiān)聽事件,接收數(shù)據(jù)
onEvent('sendData', (data) => {
dataList.value.push(data);
});
});
onUnmounted(() => {
// 組件銷毀時(shí)取消監(jiān)聽,避免內(nèi)存泄漏
offEvent('sendData');
});
</script>
<style scoped>
.data-list { margin: 20px 0; padding: 10px; border: 1px solid #eee; }
.data-list div { margin: 5px 0; }
</style>
Vue2 實(shí)現(xiàn) - 完整可運(yùn)行Demo
// 1. 注冊(cè)全局EventBus(main.js)
import Vue from 'vue';
import App from './App';
import router from './router';
// 注冊(cè)全局總線(掛載到Vue原型上)
Vue.prototype.$bus = new Vue();
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
});
// 2. 頁面A(觸發(fā)事件,發(fā)送數(shù)據(jù),@/views/PageA.vue)
<template>
<div class="page-a">
<h3>頁面A(EventBus發(fā)送數(shù)據(jù))</h3>
<input v-model="message" placeholder="請(qǐng)輸入要傳遞的內(nèi)容" />
<button @click="sendData" class="btn">發(fā)送數(shù)據(jù)到頁面B</button>
<router-link to="/pageB" class="btn">跳轉(zhuǎn)到頁面B</router-link>
</div>
</template>
<script>
export default {
data() {
return {
message: ''
};
},
methods: {
sendData() {
if (!this.message) {
alert('請(qǐng)輸入內(nèi)容');
return;
}
// 觸發(fā)全局事件,傳遞數(shù)據(jù)
this.$bus.$emit('sendData', {
message: this.message,
time: new Date().toLocaleString()
});
this.message = '';
alert('數(shù)據(jù)發(fā)送成功!');
}
}
};
</script>
// 3. 頁面B(監(jiān)聽事件,接收數(shù)據(jù),@/views/PageB.vue)
<template>
<div class="page-b">
<h3>頁面B(EventBus接收數(shù)據(jù))</h3>
<div class="data-list">
<div v-for="(item, index) in dataList" :key="index">
{{ item.time }}:{{ item.message }}
</div>
<div v-if="dataList.length === 0">暫無數(shù)據(jù)</div>
</div>
<router-link to="/pageA" class="btn">返回頁面A</router-link>
</div>
</template>
<script>
export default {
data() {
return {
dataList: []
};
},
mounted() {
// 監(jiān)聽全局事件
this.$bus.$on('sendData', (data) => {
this.dataList.push(data);
});
},
beforeDestroy() {
// 組件銷毀時(shí)取消監(jiān)聽,避免內(nèi)存泄漏
this.$bus.$off('sendData');
}
};
</script>
注意事項(xiàng)
- EventBus適合簡單通信,復(fù)雜場(chǎng)景(多頁面、多事件)易造成事件混亂,建議用Pinia/Vuex替代;
- 組件銷毀時(shí)必須取消監(jiān)聽,否則會(huì)導(dǎo)致內(nèi)存泄漏;
- 頁面未渲染時(shí),監(jiān)聽事件可能接收不到數(shù)據(jù)(需確保頁面B已渲染再觸發(fā)事件)。
五、監(jiān)聽localStorage/sessionStorage(數(shù)據(jù)變化響應(yīng))
核心思路:基于本地存儲(chǔ),監(jiān)聽存儲(chǔ)數(shù)據(jù)的變化,當(dāng)頁面A修改本地存儲(chǔ)時(shí),頁面B自動(dòng)感知并更新,解決“本地存儲(chǔ)數(shù)據(jù)變化不響應(yīng)”的問題,適合“持久化數(shù)據(jù)聯(lián)動(dòng)”場(chǎng)景。
完整可運(yùn)行Demo(Vue2/Vue3通用)
適配場(chǎng)景:頁面A修改本地存儲(chǔ)的用戶配置,頁面B自動(dòng)同步更新,無需手動(dòng)刷新。
// 1. 封裝本地存儲(chǔ)工具(utils/storage.js,同上文,可直接復(fù)用)
export const setLocal = (key, value) => {
const val = typeof value === 'object' ? JSON.stringify(value) : value;
localStorage.setItem(key, val);
};
export const getLocal = (key) => {
const val = localStorage.getItem(key);
try { return JSON.parse(val); } catch (e) { return val; }
};
// 2. 頁面A(修改本地存儲(chǔ),觸發(fā)變化,@/views/PageA.vue,Vue3示例)
<template>
<div class="page-a">
<h3>頁面A(修改本地存儲(chǔ))</h3>
<button @click="updateCount" class="btn">修改全局計(jì)數(shù)(localStorage)</button>
<div>當(dāng)前計(jì)數(shù):{{ count }}</div>
<router-link to="/pageB" class="btn">跳轉(zhuǎn)到頁面B(自動(dòng)同步)</router-link>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { setLocal, getLocal } from '@/utils/storage';
const count = ref(0);
// 頁面掛載時(shí)讀取當(dāng)前計(jì)數(shù)
onMounted(() => {
count.value = getLocal('count') || 0;
});
// 修改本地存儲(chǔ)的計(jì)數(shù)(觸發(fā)storage事件)
const updateCount = () => {
count.value++;
setLocal('count', count.value);
};
</script>
// 3. 頁面B(監(jiān)聽本地存儲(chǔ)變化,自動(dòng)更新,@/views/PageB.vue,Vue3示例)
<template>
<div class="page-b">
<h3>頁面B(監(jiān)聽本地存儲(chǔ)變化)</h3>
<div>同步的計(jì)數(shù):{{ count }}</div>
<router-link to="/pageA" class="btn">返回頁面A</router-link>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { getLocal } from '@/utils/storage';
const count = ref(0);
// 監(jiān)聽storage事件
const handleStorageChange = (e) => {
// 判斷是否是我們關(guān)注的key
if (e.key === 'count') {
count.value = Number(e.newValue);
}
};
onMounted(() => {
// 初始讀取計(jì)數(shù)
count.value = getLocal('count') || 0;
// 注冊(cè)storage監(jiān)聽
window.addEventListener('storage', handleStorageChange);
});
onUnmounted(() => {
// 取消監(jiān)聽,避免內(nèi)存泄漏
window.removeEventListener('storage', handleStorageChange);
});
</script>
// Vue2 頁面B示例
<script>
import { getLocal } from '@/utils/storage';
export default {
data() {
return { count: 0 };
},
mounted() {
this.count = getLocal('count') || 0;
window.addEventListener('storage', this.handleStorageChange);
},
beforeDestroy() {
window.removeEventListener('storage', this.handleStorageChange);
},
methods: {
handleStorageChange(e) {
if (e.key === 'count') {
this.count = Number(e.newValue);
}
}
}
};
</script>
注意事項(xiàng)
- storage事件僅在“不同頁面”間觸發(fā),同一頁面修改本地存儲(chǔ),不會(huì)觸發(fā)自身的storage事件;
- 僅能監(jiān)聽localStorage、sessionStorage的變化,無法監(jiān)聽Cookie的變化;
- 傳遞復(fù)雜數(shù)據(jù)時(shí),需配合JSON.stringify/JSON.parse解析。
六、Cookie(小型數(shù)據(jù),跨域/持久化適配)
核心思路:利用瀏覽器Cookie存儲(chǔ)小型數(shù)據(jù),可設(shè)置過期時(shí)間,支持跨域(配置domain),適合“小型持久化數(shù)據(jù)”場(chǎng)景(如用戶token、記住登錄狀態(tài)),容量較?。s4KB)。
完整可運(yùn)行Demo(Vue2/Vue3通用)
適配場(chǎng)景:存儲(chǔ)用戶登錄token、記住密碼狀態(tài),跨頁面共享,支持過期時(shí)間設(shè)置。
// 1. 封裝Cookie工具(utils/cookie.js,通用)
// 設(shè)置Cookie(支持過期時(shí)間、路徑、域名)
export const setCookie = (key, value, days = 7, path = '/', domain = '') => {
let cookieStr = `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
// 設(shè)置過期時(shí)間
if (days) {
const date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
cookieStr += `;expires=${date.toUTCString()}`;
}
// 設(shè)置路徑
if (path) cookieStr += `;path=${path}`;
// 設(shè)置域名(跨域時(shí)使用)
if (domain) cookieStr += `;domain=${domain}`;
// 寫入Cookie
document.cookie = cookieStr;
};
// 獲取Cookie
export const getCookie = (key) => {
const cookies = document.cookie.split('; ');
for (const cookie of cookies) {
const [k, v] = cookie.split('=');
if (k === encodeURIComponent(key)) {
return decodeURIComponent(v);
}
}
return '';
};
// 刪除Cookie
export const removeCookie = (key, path = '/', domain = '') => {
// 設(shè)置過期時(shí)間為過去,實(shí)現(xiàn)刪除
setCookie(key, '', -1, path, domain);
};
// 2. 頁面A(設(shè)置Cookie,@/views/PageA.vue,Vue3示例)
<template>
<div class="page-a">
<h3>頁面A(設(shè)置Cookie)</h3>
<div>
<label>記住密碼:</label>
<input type="checkbox" v-model="remember" />
</div>
<button @click="login" class="btn">模擬登錄(設(shè)置Cookie)</button>
<button @click="removeCookie('token')" class="btn">刪除token</button>
<router-link to="/pageB" class="btn">跳轉(zhuǎn)到頁面B讀取Cookie</router-link>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { setCookie, removeCookie } from '@/utils/cookie';
const remember = ref(false);
// 模擬登錄,設(shè)置Cookie
const login = () => {
const token = 'abc123456789'; // 模擬后端返回的token
// 記住密碼:保存7天;不記?。翰辉O(shè)置過期時(shí)間(會(huì)話結(jié)束后失效)
setCookie('token', token, remember.value ? 7 : 0);
alert('登錄成功,Cookie已設(shè)置!');
};
</script>
// 3. 頁面B(讀取Cookie,@/views/PageB.vue,Vue3示例)
<template>
<div class="page-b">
<h3>頁面B(讀取Cookie)</h3>
<div>當(dāng)前登錄token:{{ token }}</div>
<div v-if="!token">未獲取到token(Cookie已過期或未設(shè)置)</div>
<router-link to="/pageA" class="btn">返回頁面A</router-link>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { getCookie } from '@/utils/cookie';
const token = ref('');
// 頁面掛載時(shí)讀取Cookie
onMounted(() => {
token.value = getCookie('token');
});
</script>
// Vue2 頁面B示例
<script>
import { getCookie } from '@/utils/cookie';
export default {
data() {
return { token: '' };
},
mounted() {
this.token = getCookie('token');
}
};
</script>
注意事項(xiàng)
- Cookie容量約4KB,僅適合存儲(chǔ)小型數(shù)據(jù)(如token、用戶ID);
- 默認(rèn)隨每次請(qǐng)求發(fā)送到后端,可用于前后端共享數(shù)據(jù)(如身份驗(yàn)證);
- 敏感數(shù)據(jù)(如token)建議加密后存儲(chǔ),避免泄露。
七、窗口通信(window.postMessage,跨域/多窗口適配)
核心思路:通過window.postMessage方法,實(shí)現(xiàn)不同頁面(甚至跨域頁面、多窗口)間的通信,適合“跨域頁面、多窗口聯(lián)動(dòng)”場(chǎng)景(如Vue頁面與iframe頁面通信、打開新窗口傳值)。
完整可運(yùn)行Demo(分2種場(chǎng)景,Vue2/Vue3通用)
場(chǎng)景1:頁面A打開新窗口(頁面B),傳遞數(shù)據(jù)
// 頁面A(打開新窗口,發(fā)送數(shù)據(jù),@/views/PageA.vue,Vue3示例)
<template>
<div class="page-a">
<h3>頁面A(打開新窗口傳值)</h3>
<button @click="openPageB" class="btn">打開頁面B并傳遞數(shù)據(jù)</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
// 存儲(chǔ)新窗口實(shí)例
const pageB = ref(null);
// 打開頁面B并發(fā)送數(shù)據(jù)
const openPageB = () => {
// 打開新窗口(同域場(chǎng)景,路徑為頁面B的路由)
pageB.value = window.open('/pageB', '_blank');
// 確保頁面B加載完成后發(fā)送數(shù)據(jù)(避免接收不到)
setTimeout(() => {
// 發(fā)送數(shù)據(jù):第一個(gè)參數(shù)是數(shù)據(jù),第二個(gè)參數(shù)是目標(biāo)域名(*表示允許所有,生產(chǎn)環(huán)境需指定具體域名)
pageB.value.postMessage(
{ type: 'init', data: { id: 123, name: '窗口通信Demo' } },
'http://localhost:8080' // 生產(chǎn)環(huán)境替換為實(shí)際域名
);
}, 1000);
};
</script>
// 頁面B(新窗口,接收數(shù)據(jù),@/views/PageB.vue,Vue3示例)
<template>
<div class="page-b">
<h3>頁面B(新窗口接收數(shù)據(jù))</h3>
<div v-if="receivedData">
<div>接收的數(shù)據(jù)類型:{{ receivedData.type }}</div>
<div>接收的ID:{{ receivedData.data.id }}</div>
<div>接收的名稱:{{ receivedData.data.name }}</div>
</div>
<div v-else>暫無數(shù)據(jù)接收</div>
<button @click="sendBackData" class="btn">向頁面A返回?cái)?shù)據(jù)</button>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const receivedData = ref(null);
// 接收頁面A發(fā)送的數(shù)據(jù)
const handleMessage = (e) => {
// 安全校驗(yàn):只接收指定域名的消息(生產(chǎn)環(huán)境必加,防止惡意消息)
if (e.origin !== 'http://localhost:8080') return;
// 接收數(shù)據(jù)并賦值
receivedData.value = e.data;
};
// 向頁面A返回?cái)?shù)據(jù)
const sendBackData = () => {
if (!receivedData.value) {
alert('未接收任何數(shù)據(jù),無法返回');
return;
}
// 通過opener獲取父窗口(頁面A),發(fā)送返回?cái)?shù)據(jù)
window.opener.postMessage(
{ type: 'reply', data: '已成功接收數(shù)據(jù),感謝發(fā)送!' },
'http://localhost:8080'
);
alert('返回?cái)?shù)據(jù)已發(fā)送給頁面A');
};
onMounted(() => {
// 注冊(cè)message監(jiān)聽
window.addEventListener('message', handleMessage);
});
onUnmounted(() => {
// 取消監(jiān)聽,避免內(nèi)存泄漏
window.removeEventListener('message', handleMessage);
});
</script>
// Vue2 頁面B示例(@/views/PageB.vue)
<script>
export default {
data() {
return {
receivedData: null
};
},
mounted() {
window.addEventListener('message', this.handleMessage);
},
beforeDestroy() {
window.removeEventListener('message', this.handleMessage);
},
methods: {
handleMessage(e) {
if (e.origin !== 'http://localhost:8080') return;
this.receivedData = e.data;
},
sendBackData() {
if (!this.receivedData) {
alert('未接收任何數(shù)據(jù),無法返回');
return;
}
window.opener.postMessage(
{ type: 'reply', data: '已成功接收數(shù)據(jù),感謝發(fā)送!' },
'http://localhost:8080'
);
alert('返回?cái)?shù)據(jù)已發(fā)送給頁面A');
}
}
};
</script>
// 補(bǔ)充:頁面A接收頁面B返回?cái)?shù)據(jù)(Vue3示例,添加到PageA的script中)
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const pageB = ref(null);
const replyData = ref('');
// 接收頁面B返回的數(shù)據(jù)
const handleReply = (e) => {
if (e.origin !== 'http://localhost:8080') return;
replyData.value = e.data.data;
alert(`收到頁面B返回:${replyData.value}`);
};
const openPageB = () => {
pageB.value = window.open('/pageB', '_blank');
setTimeout(() => {
pageB.value.postMessage(
{ type: 'init', data: { id: 123, name: '窗口通信Demo' } },
'http://localhost:8080'
);
}, 1000);
};
onMounted(() => {
window.addEventListener('message', handleReply);
});
onUnmounted(() => {
window.removeEventListener('message', handleReply);
});
</script>
// 場(chǎng)景2:Vue頁面與iframe頁面通信(跨域/同域通用)
// 1. 頁面A(包含iframe,發(fā)送數(shù)據(jù),@/views/PageA.vue,Vue3示例)
<template>
<div class="page-a">
<h3>頁面A(iframe通信)</h3>
<!-- iframe嵌入頁面B(可跨域,如https://xxx.com/pageB) -->
<iframe
ref="iframeRef"
src="http://localhost:8080/pageB" // 替換為實(shí)際iframe地址
width="800"
height="400"
></iframe>
<button @click="sendToIframe" class="btn" style="margin-top: 20px;">向iframe發(fā)送數(shù)據(jù)</button>
<div>iframe返回?cái)?shù)據(jù):{{ iframeReply }}</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const iframeRef = ref(null);
const iframeReply = ref('');
// 向iframe發(fā)送數(shù)據(jù)
const sendToIframe = () => {
if (!iframeRef.value) return;
// 獲取iframe的window對(duì)象,發(fā)送數(shù)據(jù)
iframeRef.value.contentWindow.postMessage(
{ type: 'iframeData', data: '來自Vue頁面的iframe通信數(shù)據(jù)' },
'http://localhost:8080' // 跨域時(shí)填寫iframe的實(shí)際域名
);
};
// 接收iframe返回的數(shù)據(jù)
const handleIframeReply = (e) => {
if (e.origin !== 'http://localhost:8080') return;
iframeReply.value = e.data.data;
};
onMounted(() => {
window.addEventListener('message', handleIframeReply);
// 等待iframe加載完成(可選,確保通信穩(wěn)定)
iframeRef.value.onload = () => {
console.log('iframe加載完成,可開始通信');
};
});
onUnmounted(() => {
window.removeEventListener('message', handleIframeReply);
});
</script>
// 2. 頁面B(iframe內(nèi)嵌頁面,接收/返回?cái)?shù)據(jù),@/views/PageB.vue,Vue3示例)
<template>
<div class="page-b">
<h3>iframe內(nèi)嵌頁面(接收數(shù)據(jù))</h3>
<div>接收的iframe數(shù)據(jù):{{ iframeData }}</div>
<button @click="replyToParent" class="btn">向父頁面返回?cái)?shù)據(jù)</button>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const iframeData = ref('暫無數(shù)據(jù)');
// 接收父頁面(頁面A)發(fā)送的數(shù)據(jù)
const handleParentMessage = (e) => {
// 安全校驗(yàn),僅接收指定域名的消息
if (e.origin !== 'http://localhost:8080') return;
iframeData.value = e.data.data;
};
// 向父頁面返回?cái)?shù)據(jù)
const replyToParent = () => {
// 通過parent獲取父窗口,發(fā)送返回?cái)?shù)據(jù)
window.parent.postMessage(
{ type: 'iframeReply', data: '已接收iframe數(shù)據(jù),返回確認(rèn)!' },
'http://localhost:8080'
);
};
onMounted(() => {
window.addEventListener('message', handleParentMessage);
});
onUnmounted(() => {
window.removeEventListener('message', handleParentMessage);
});
</script>
// Vue2 iframe通信示例(頁面B)
<script>
export default {
data() {
return {
iframeData: '暫無數(shù)據(jù)'
};
},
mounted() {
window.addEventListener('message', this.handleParentMessage);
},
beforeDestroy() {
window.removeEventListener('message', this.handleParentMessage);
},
methods: {
handleParentMessage(e) {
if (e.origin !== 'http://localhost:8080') return;
this.iframeData = e.data.data;
},
replyToParent() {
window.parent.postMessage(
{ type: 'iframeReply', data: '已接收iframe數(shù)據(jù),返回確認(rèn)!' },
'http://localhost:8080'
);
}
}
};
</script>
以上就是Vue中跨頁面通信的8種主流方式詳解的詳細(xì)內(nèi)容,更多關(guān)于Vue跨頁面通信的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于ElementUI實(shí)現(xiàn)v-tooltip指令的示例代碼
文本溢出隱藏并使用tooltip 提示的需求,相信在平時(shí)的開發(fā)中會(huì)經(jīng)常遇到,常規(guī)做法一般是使用 elementui 的 el-tooltip 組件,在每次組件更新的時(shí)候去獲取元素的寬度/高度判斷是否被隱藏,本文給大家介紹了基于ElementUI實(shí)現(xiàn)v-tooltip指令,需要的朋友可以參考下2024-09-09
vue3中<script?setup>?和?setup函數(shù)的區(qū)別對(duì)比
這篇文章主要介紹了vue3中<script?setup>?和?setup函數(shù)的區(qū)別,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04
解決vue-cli webpack打包開啟Gzip 報(bào)錯(cuò)問題
這篇文章主要介紹了vue-cli webpack打包開啟Gzip 報(bào)錯(cuò)問題的解決方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-07-07

