項目需求:一個路由頁面下展示多個頁面,且切換時要像真正的路由一樣可以攜帶路由參數
三個前置知識:
- vue不允許在路由棧中推入同一URL地址,否則會告警:
NavigationDuplicated: Avoided redundant navigation to current location: "/route".
- 在以
react、vue
爲代表的SPA項目中,在萬物皆組件的今天,所謂路由的切換其實僅是不同組件的切換而已。(寫到這裏,不由得想到一句話:我們從html學到了js再學到以vue/react爲代表的生態框架,本質上也不過在一個div裏打打鬧鬧而已,這個div叫#app
...)
- 路由間跳轉時如果有參且參數是
query
類型時,在刷新時將不會丟失參數
基於項目需求以及前置知識:
- 畫出了思維導圖
- 設計基礎邏輯
// page.vue
<template>
<div>
<p1 v-if="currentNode === 1"/>
<p2 v-if="currentNode === 2"/>
<p3 v-if="currentNode === 3"/>
</div>
</template>
<script>
import p1 from './p1.vue'
import p2 from './p2.vue'
import p3 from './p3.vue'
export default {
components: {
p1,
p2,
p3
},
data() {
return {
currentNode : 1 // 當前展示組件
}
}
}
</script>
一:修復重複告警問題:
爲了規避前置1產生的問題,重寫了routerInstance
方法
// ./router/index.js
// 重寫路由push方法以規避`NavigationDuplicated: Avoided redundant navigation to current location: "/route".`告警
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch((err) => err);
};
二:設計組件切換邏輯
這裏其實想了很多種方法,原先想使用
window.location.href + query
的方式,但是由於此方法會導致頁面白屏而放棄,後來又嘗試在每個子組件中emit
對應的值來使當前組件動態切換, 但總覺得不夠優雅,遂將其作爲兜底方案,直到後來想到了終極解決方案後便放棄了此方案
終極方案便是利用watch+routerQuery。
即:
index頁監聽routeQuery
對象,各個子組件內像使用路由跳轉一樣跳轉(僅支持push
),而watch
存在的目的在於通過監聽query的參數以進行組件間的切換。
- 淺灰標識跳轉至,深灰標識回退至
貼貼:
// page.vue
watch: {
// 模擬路由跳轉 -》`routerInstance`重寫了router的`push`方法,因而不會告警
// 通過監聽路由參數進行組件間的切換 子組件可以直接使用this.$router.push(xxx)以達到類路由跳轉效果
/**
* A : 初始頁: 去中間: query1 去末 query2
* B: 中間頁 去末:query1 + query2 回初始不帶參
* C: 末頁 回中間:query2 回初始不帶參
* */
"$route.query": {
handler(n) {
// 如果沒有query,則展示組件1
if (!Object.values(n).length) {
this.currentNode = 1;
return;
}
// 如果有組件2必須的query1參數且query參數爲1,則展示組件2
if (Object.values(n).length === 1 && `query1` in n) {
this.currentNode = 2;
return;
}
// 僅判斷組件3必須參數query2,有則展示組件3
if (`query2` in n) {
this.currentNode = 3;
return;
}
// 除此以外,均展示組件1
this.currentNode = 1;
},
deep: true,
immediate: true,
},
},
在從中間頁前往末頁時可能並不需要query1
(中間頁本身的參數),但是我們依然要加上去,這樣傳參的目的是爲了區分末頁的上一頁是誰以實現back
功能(見圖2)
而當我們在page頁中寫好了以上邏輯以後,就可以在子組件內像跳轉正常路由一樣跳轉了,不同的是跳轉的是同一個路由頁,只不過參數不同,這也是我們在寫這一類場景時要注意的點之一
假設page頁
的路由名是/wzdxcsk
(我真的想喫燒烤),則在組件1
中想跳轉組件2/組件3
則可以:
//p1.vue
// 組件1動態跳轉組件2/組件3
methods:{
navToComp2(id,type){
// 動態跳轉
const queryObj = {
2: {
query1: id,
},
3: {
query2: id,
},
};
this.$router.push({
path: "/wzdxcsk",
query: queryObj[type],
});
},
}
假設組件3想要回退至上一頁則可以:
//p3.vue
// 組件3動態跳轉組件2/組件1
methods:{
// 回退
back() {
this.$router.push({
path: "/wzdxcsk",
// 回退校驗
query:
`query1` in this.$route.query
? {
query1: this.$route.query.query1,
}
: {},
});
},
}
整體邏輯:
在單頁面中通過變量
currentNode
來控制子組件顯隱,通過watch路由參數來控制具體哪個子組件顯示,通過重寫routerInstance實例的push方法來規避重複推入相同路由告警,子組件通過常規push方法實現回退&前進,前進回退時通過唯一query來判定跳轉的子組件頁面,由於url上有路由參數,則頁面在刷新後依然可以根據watch
自動展示不同的組件 。
希望讀者在遇到此類需求時可以有一個參考
以上。