基于Vue3+Pinia的移動端頁面緩存策略的解決方案
第1步:緩存策略的目的與意義
在移動端Web應(yīng)用中,咱們經(jīng)常遇到這樣的用戶場景:
- 商品列表 → 商品詳情 → 返回列表:用戶希望回到之前瀏覽的位置。
- 菜單 → 商品列表:用戶希望看到最新的商品信息。
- 表單填寫過程:用戶在多個步驟(子頁面)間切換時保持已填寫的數(shù)據(jù)。
傳統(tǒng)方案的痛點:
// 傳統(tǒng)keep-alive方案的問題 <keep-alive :include="['ProductList', 'UserProfile']"> <router-view /> </keep-alive>
- 問題1:無法區(qū)分進(jìn)入方式(從菜單進(jìn)入 vs 從其他頁面返回)。
- 問題2:緩存策略過于粗暴,要么全緩存要么不緩存。
- 問題3:無法根據(jù)業(yè)務(wù)邏輯動態(tài)控制緩存行為。
第2步:實現(xiàn)原理與核心設(shè)計思想
核心設(shè)計理念
咱們的方案基于一個簡單的思想:模擬整個應(yīng)用的"導(dǎo)航棧"行為。
用戶操作流程:菜單頁 → 頁面A → 頁面B → 返回A → 返回菜單頁 → 重新進(jìn)入A 期望行為: 清空 → 新建A → 緩存A → 恢復(fù)A → 清空 → 新建A
菜單頁概念說明
在開始介紹下面內(nèi)容之前,咱們先明確一個重要概念:菜單頁。
什么是菜單頁?
答:它類似于App的首頁或主導(dǎo)航頁面。
為什么需要菜單頁?
答:定期清理緩存,避免內(nèi)存泄漏,確保用戶每次從主入口開始都是干凈的狀態(tài)。
原理流程圖
一圖勝千言,瞧瞧:

關(guān)鍵技術(shù)點
實時導(dǎo)航棧管理
// 檢查是否為返回到已存在的路由
const isBackToExistingRoute = (targetFullPath) => {
return state.navigationStack.some(route => route.fullPath === targetFullPath);
};
// 通過檢查導(dǎo)航棧判斷是前進(jìn)還是返回
const isBackNavigation = isBackToExistingRoute(to.fullPath);
if (isBackNavigation) {
// 返回操作:移除目標(biāo)路由之后的所有路由
backToRoute(to.fullPath);
} else {
// 前進(jìn)操作:添加新路由到棧中,緩存離開的頁面
pushRoute(to.fullPath, to.name);
if (from.meta?.keepAlive && from.name) {
state.cachedComponents.add(from.name);
}
}
緩存時機(jī)
- 前進(jìn)導(dǎo)航:緩存離開的頁面(如果配置了keepAlive)
- 返回導(dǎo)航:清理目標(biāo)頁面之后的所有緩存
- 菜單重置:清空所有緩存,確保新鮮狀態(tài)
第3步:核心代碼實現(xiàn)
在實際項目中,小編是使用 Pinia 來完成這塊緩存功能的開發(fā),具體如下:
import { reactive, toRefs, computed } from "vue";
import { defineStore } from "pinia";
export const useRouteCacheStore = defineStore("routeCache", () => {
const state = reactive({
navigationStack: [], // 導(dǎo)航歷史棧 [{fullPath, name}]
cachedComponents: new Set(), // 緩存的組件名稱列表
menuPath: '/menu' // 菜單頁面路徑
});
/**
* 計算屬性:緩存組件列表
* @description 將 Set 類型的緩存組件轉(zhuǎn)換為數(shù)組格式
* @returns {string[]} 緩存的組件名稱數(shù)組
*/
const cacheIncludeList = computed(() => {
return Array.from(state.cachedComponents);
});
/**
* 檢查是否為返回到已存在的路由
* @description 通過檢查導(dǎo)航棧中是否存在目標(biāo)路徑來判斷是否為返回操作
* @param {string} targetFullPath - 目標(biāo)路由的完整路徑
* @returns {boolean} 如果路由已存在于導(dǎo)航棧中返回 true,否則返回 false
*/
const isBackToExistingRoute = (targetFullPath) => {
return state.navigationStack.some(route => route.fullPath === targetFullPath);
};
/**
* 添加路由到棧中
* @description 如果路由不存在于導(dǎo)航棧中,則將其添加到棧頂
* @param {string} fullPath - 路由的完整路徑
* @param {string} name - 路由的名稱
* @returns {void}
*/
const pushRoute = (fullPath, name) => {
if (!isBackToExistingRoute(fullPath)) {
state.navigationStack.push({ fullPath, name });
}
};
/**
* 返回到指定路由,移除后續(xù)路由
* @description 找到目標(biāo)路由在棧中的位置,移除其后的所有路由及其緩存,然后截斷導(dǎo)航棧
* @param {string} targetFullPath - 目標(biāo)路由的完整路徑
* @returns {void}
*/
const backToRoute = (targetFullPath) => {
const targetIndex = state.navigationStack.findIndex(route => route.fullPath === targetFullPath);
if (targetIndex !== -1) {
// 移除目標(biāo)路由之后的所有路由的緩存
const removedRoutes = state.navigationStack.slice(targetIndex + 1);
removedRoutes.forEach(route => {
if (route.name) {
state.cachedComponents.delete(route.name);
}
});
// 截斷導(dǎo)航棧
state.navigationStack = state.navigationStack.slice(0, targetIndex + 1);
}
};
/**
* 處理路由導(dǎo)航
* @description 根據(jù)導(dǎo)航類型(前進(jìn)/返回)處理路由棧和組件緩存,進(jìn)入菜單頁時清空所有緩存
* @param {Object} to - 目標(biāo)路由對象
* @param {string} to.fullPath - 目標(biāo)路由的完整路徑
* @param {string} to.name - 目標(biāo)路由的名稱
* @param {Object} to.meta - 目標(biāo)路由的元信息
* @param {Object} from - 來源路由對象
* @param {string} from.fullPath - 來源路由的完整路徑
* @param {string} from.name - 來源路由的名稱
* @param {Object} from.meta - 來源路由的元信息
* @param {boolean} from.meta.keepAlive - 是否需要緩存該組件
* @returns {void}
*/
const handleNavigation = (to, from) => {
const isBackNavigation = isBackToExistingRoute(to.fullPath);
if (isBackNavigation) {
// 返回操作:移除目標(biāo)路由之后的所有路由
backToRoute(to.fullPath);
} else {
// 前進(jìn)操作:添加新路由到棧中
pushRoute(to.fullPath, to.name);
if (from.meta?.keepAlive && from.name) {
state.cachedComponents.add(from.name);
}
}
// 進(jìn)入菜單頁時,清空所有緩存
if (to.fullPath === state.menuPath) {
state.cachedComponents.clear();
}
};
return {
...toRefs(state),
cacheIncludeList,
handleNavigation,
};
});
沒多少代碼哈,小編也都貼心寫明了詳細(xì)注釋,應(yīng)該都能讀懂哈。
然后,就是在路由守衛(wèi)這里統(tǒng)一攔截所有的路由進(jìn)行處理:
import router from "@/router";
import { useRouteCacheStore } from '@/stores/modules/routeCache'
router.beforeEach((to, from, next) => {
const routeCacheStore = useRouteCacheStore()
// 處理路由緩存邏輯
routeCacheStore.handleNavigation(to, from)
next()
})
最后,在組件視圖這塊應(yīng)用動態(tài)緩存數(shù)據(jù):
<!-- src/layouts/BasicLayout.vue -->
<template>
<div class="basic-layout">
<keep-alive :include="cacheIncludeList">
<router-view />
</keep-alive>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useRouteCacheStore } from '@/stores/modules/routeCache'
const routeCacheStore = useRouteCacheStore()
/** @description 獲取需要緩存的組件列表 */
const cacheIncludeList = computed(() => routeCacheStore.cacheIncludeList)
</script>
在路由配置中,記得標(biāo)明哪些路由是需要緩存的即可,示例:
// src/router/index.js
const routes = [
{
path: '/menu',
name: 'Menu',
component: () => import('@/views/menu/index.vue')
},
{
path: '/product-list',
name: 'ProductList',
component: () => import('@/views/product/list.vue'),
meta: {
keepAlive: true // 標(biāo)記需要緩存
}
},
{
path: '/product-detail/:id',
name: 'ProductDetail',
component: () => import('@/views/product/detail.vue')
// 詳情頁不需要緩存
}
]
第4步:技術(shù)細(xì)節(jié)解析
fullPath的重要性
在移動端應(yīng)用中,同一個頁面可能因為不同的查詢參數(shù)而展示不同的內(nèi)容:
// 場景1:不同分類的商品列表 /product-list?category=phone&sort=price /product-list?category=laptop&sort=sales // 場景2:不同頁碼的列表 /product-list?page=1 /product-list?page=3
使用fullPath可以正確區(qū)分這些頁面,避免緩存混亂。
實時棧管理的優(yōu)勢
傳統(tǒng)方案:棧不實時更新,容易出現(xiàn)判斷錯誤。如:
菜單 → A → 菜單 → A (第二次進(jìn)入A時,棧中還有A,誤判為返回操作)
咱們的方案:實時移除離開的頁面。如:
菜單 → A(棧:[A]) → 菜單(棧:[],A被移除) → A(棧:[A],正確判斷為新進(jìn)入)
總結(jié)
這套基于導(dǎo)航棧的智能緩存策略完美解決了移動端頁面緩存難題!核心優(yōu)勢包括精準(zhǔn)控制緩存時機(jī)、保持頁面狀態(tài)同時確保數(shù)據(jù)新鮮度,應(yīng)該比較適合那些移動端Web應(yīng)用。
以上就是基于Vue3+Pinia的移動端頁面緩存策略的解決方案的詳細(xì)內(nèi)容,更多關(guān)于Vue3 Pinia移動端頁面緩存策略的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue?elementUI實現(xiàn)樹形結(jié)構(gòu)表格與懶加載
這篇文章主要介紹了通過Vue和elementUI實現(xiàn)樹形結(jié)構(gòu)表格與懶加載,文中的示例代碼講解詳細(xì),感興趣的小伙伴快來跟隨小編一起學(xué)習(xí)一下吧2022-01-01
vue-element-admin開發(fā)教程(v4.0.0之后)
由于vue-element-admin的架構(gòu)再4.0.0版本后做了重構(gòu),整體結(jié)構(gòu)上和之前的還是很相似的,但是也有些變化,本文就介紹vue-element-admin開發(fā)教程(v4.0.0之后),感興趣的可以了解一下2022-04-04

