福州市建设工程材料价格管理系统网站,申请建设项目立项备案网站,怎么做电商网站,手机网站按那个尺寸做有这么一个需求#xff1a;列表页进入详情页后#xff0c;切换回列表页#xff0c;需要对列表页进行缓存#xff0c;如果从首页进入列表页#xff0c;就要重新加载列表页。
对于这个需求#xff0c;我的第一个想法就是使用keep-alive来缓存列表页#xff0c;列表和详情…有这么一个需求列表页进入详情页后切换回列表页需要对列表页进行缓存如果从首页进入列表页就要重新加载列表页。
对于这个需求我的第一个想法就是使用keep-alive来缓存列表页列表和详情页切换时列表页会被缓存从首页进入列表页时就重置列表页数据并重新获取新数据来达到列表页重新加载的效果。
但是这个方案有个很不好的地方就是如果列表页足够复杂有下拉刷新、下拉加载、有弹窗、有轮播等在清除缓存时就需要重置很多数据和状态而且还可能要手动去销毁和重新加载某些组件这样做既增加了复杂度也容易出bug。
接下来说说我的想到的新实现方案代码基于Vue3。
keep-alive 缓存和清除 keep-alive 缓存原理进入页面时页面组件渲染完成keep-alive 会缓存页面组件的实例离开页面后组件实例由于已经缓存就不会进行销毁当再次进入页面时就会将缓存的组件实例拿出来渲染因为组件实例保存着原来页面的数据和Dom的状态那么直接渲染组件实例就能得到原来的页面。 keep-alive 最大的难题就是缓存的清理如果能有简单的缓存清理方法那么keep-alive 组件用起来就很爽。
但是keep-alive 组件没有提供清除缓存的API那有没有其他清除缓存的办法呢答案是有的。我们先看看 keep-alive 组件的props
include - string | RegExp | Array。只有名称匹配的组件会被缓存。
exclude - string | RegExp | Array。任何名称匹配的组件都不会被缓存。
max - number | string。最多可以缓存多少组件实例。
从include描述来看我发现include是可以用来清除缓存做法是将组件名称添加到include里组件会被缓存移除组件名称组件缓存会被清除。根据这个原理用hook简单封装一下代码
import { ref, nextTick } from vueconst caches refstring[]([])export default function useRouteCache () {// 添加缓存的路由组件function addCache (componentName: string | string []) {if (Array.isArray(componentName)) {componentName.forEach(addCache)return}if (!componentName || caches.value.includes(componentName)) returncaches.value.push(componentName)}// 移除缓存的路由组件function removeCache (componentName: string) {const index caches.value.indexOf(componentName)if (index -1) {return caches.value.splice(index, 1)}}// 移除缓存的路由组件的实例async function removeCacheEntry (componentName: string) { if (removeCache(componentName)) {await nextTick()addCache(componentName)}}return {caches,addCache,removeCache,removeCacheEntry}
}
hook的用法如下
router-view v-slot{ Component }keep-alive :includecachescomponent :isComponent //keep-alive
/router-viewscript setup langts
import useRouteCache from ./hooks/useRouteCache
const { caches, addCache } useRouteCache()!-- 将列表页组件名称添加到需要缓存名单中 --
addCache([List])
/script
清除列表页缓存如下
import useRouteCache from /hooks/useRouteCacheconst { removeCacheEntry } useRouteCache()
removeCacheEntry(List) 此处removeCacheEntry方法清除的是列表组件的实例List 值仍然在 组件的include里下次重新进入列表页会重新加载列表组件并且之后会继续列表组件进行缓存。 列表页清除缓存的时机
进入列表页后清除缓存
在列表页路由组件的beforeRouteEnter勾子中判断是否是从其他页面Home进入的是则清除缓存不是则使用缓存。
defineOptions({name: List1,beforeRouteEnter (to: RouteRecordNormalized, from: RouteRecordNormalized) {if (from.name Home) {const { removeCacheEntry } useRouteCache()removeCacheEntry(List1)}}
})
这种缓存方式有个不太友好的地方当从首页进入列表页列表页和详情页来回切换列表页是缓存的但是在首页和列表页间用浏览器的前进后退来切换时我们更多的是希望列表页能保留缓存就像在多页面中浏览器前进后退会缓存原页面一样的效果。但实际上列表页重新刷新了这就需要使用另一种解决办法点击链接时清除缓存清除缓存。
点击链接跳转前清除缓存
在首页点击跳转列表页前在点击事件的时候去清除列表页缓存这样的话在首页和列表页用浏览器的前进后退来回切换列表页都是缓存状态只要当重新点击跳转链接的时候才重新加载列表页满足预期。
// 首页 Home.vuelirouter-link to/list clickremoveCacheBeforeEnter列表页/router-link
/liscript setup langts
import useRouteCache from /hooks/useRouteCachedefineOptions({name: Home
})const { removeCacheEntry } useRouteCache()// 进入页面前先清除缓存实例
function removeCacheBeforeEnter () {removeCacheEntry(List)
}
/script
状态管理实现缓存
通过状态管理库存储页面的状态和数据也能实现页面缓存。此处状态管理使用的是pinia。
首先使用pinia创建列表页store
import { defineStore } from piniainterface Item {id?: number,content?: string
}const useListStore defineStore(list, {// 推荐使用 完整类型推断的箭头函数state: () {return {isRefresh: true,pageSize: 30,currentPage: 1,list: [] as Item[],curRow: null as Item | null}},actions: {setList (data: Item []) {this.list data},setCurRow (data: Item) {this.curRow data},setIsRefresh (data: boolean) {this.isRefresh data}}
})export default useListStore
然后在列表页中使用store
divel-page-header backgoBacktemplate #content状态管理实现列表页缓存/template/el-page-headerel-table v-loadingloading :datatableData border stylewidth: 100%; margin-top: 30px;el-table-column propid labelid /el-table-column propcontent label内容/el-table-column label操作template v-slot{ row }el-link typeprimary clickgotoDetail(row)进入详情/el-linkel-tag typesuccess v-ifrow.id listStore.curRow?.id刚点击/el-tag/template/el-table-column/el-tableel-paginationv-model:currentPagelistStore.currentPage:page-sizelistStore.pageSizelayouttotal, prev, pager, next:totallistStore.list.length/
/divscript setup langts
import useListStore from /store/listStore
const listStore useListStore()...
/script
通过beforeRouteEnter钩子判断是否从首页进来是则通过 listStore.$reset() 来重置数据否则使用缓存的数据状态之后根据 listStore.isRefresh 标示判断是否重新获取列表数据。
defineOptions({beforeRouteEnter (to: RouteLocationNormalized, from: RouteLocationNormalized) {if (from.name Home) {const listStore useListStore()listStore.$reset()}}
})onBeforeMount(() {if (!listStore.useCache) {loading.value truesetTimeout(() {listStore.setList(getData())loading.value false}, 1000)listStore.useCache true}
})
缺点
通过状态管理去做缓存的话需要将状态数据都存在stroe里状态多起来的话会有点繁琐而且状态写在store里肯定没有写在列表组件里来的直观状态管理由于只做列表页数据的缓存对于一些非受控组件来说组件内部状态改变是缓存不了的这就导致页面渲染后跟原来有差别需要额外代码操作。
页面弹窗实现缓存
将详情页做成全屏弹窗那么从列表页进入详情页就只是简单地打开详情页弹窗将列表页覆盖从而达到列表页 “缓存”的效果而非真正的缓存。
这里还有一个问题打开详情页之后如果点后退会返回到首页实际上我们希望是返回列表页这就需要给详情弹窗加个历史记录如列表页地址为 /list打开详情页变为 /list?id1。
弹窗组件实现
// PopupPage.vuetemplatediv classpopup-page :class[!dialogVisible hidden]slot v-ifdialogVisible/slot/div
/templatescript setup langts
import { useLockscreen } from element-plus
import { computed, defineProps, defineEmits } from vue
import useHistoryPopup from ./useHistoryPopupconst props defineProps({modelValue: {type: Boolean,default: false},// 路由记录history: {type: Object},// 配置了history后初次渲染时如果有url上有history参数则自动打开弹窗auto: {type: Boolean,default: true},size: {type: String,default: 50%},full: {type: Boolean,default: false}
})
const emit defineEmits([update:modelValue, autoOpen, autoClose]
)const dialogVisible computedboolean({ // 控制弹窗显示get () {return props.modelValue},set (val) {emit(update:modelValue, val)}
})useLockscreen(dialogVisible)useHistoryPopup({history: computed(() props.history),auto: props.auto,dialogVisible: dialogVisible,onAutoOpen: () emit(autoOpen),onAutoClose: () emit(autoClose)
})
/scriptstyle langless
.popup-page {position: fixed;left: 0;right: 0;top: 0;bottom: 0;z-index: 100;overflow: auto;padding: 10px;background: #fff;.hidden {display: none;}
}
/style
弹窗组件调用
popup-page v-modelvisible full:history{ id: id }Detail/Detail
/popup-page
缺点
弹窗实现页面缓存局限比较大只能在列表页和详情页中才有效离开列表页之后缓存就会失效比较合适一些简单缓存的场景。
父子路由实现缓存
该方案原理其实就是页面弹窗列表页为父路由详情页为子路由从列表页跳转到详情页时显示详情页字路由且详情页全屏显示覆盖住列表页。
声明父子路由
{path: /list,name: list,component: () import(./views/List.vue),children: [{path: /detail,name: detail,component: () import(./views/Detail.vue),}]
}
列表页代码
// 列表页
templateel-table v-loadingloading :datatableData border stylewidth: 100%; margin-top: 30px;el-table-column propid labelid /el-table-column propcontent label内容/el-table-column label操作template v-slot{ row }el-link typeprimary clickgotoDetail(row)进入详情/el-linkel-tag typesuccess v-ifrow.id curRow?.id刚点击/el-tag/template/el-table-column/el-tableel-paginationv-model:currentPagecurrentPage:page-sizepageSizelayouttotal, prev, pager, next:totallist.length/!-- 详情页 --router-view classpopyp-page/router-view
/templatestyle langless scoped
.popyp-page {position: fixed;top: 0;bottom: 0;left: 0;right: 0;z-index: 100;background: #fff;overflow: auto;
}
/style