In addition to keep-alive, what are the implementations of page caching in Vue3

Table of contents

introduction

save flow

keep-alive cache and purge

When the list page clears the cache

Clear the cache after entering the list page

Clear the cache before clicking the link

State management implements caching

shortcoming

Page pop-up window realizes caching

shortcoming

Parent-child routing implements caching


introduction

        There is such a requirement: after the list page enters the details page and switches back to the list page, the list page needs to be cached. If the list page is entered from the home page, the list page must be reloaded.

        For this requirement, my first idea is to use keep-alive to cache the list page. When the list and details page are switched, the list page will be cached; when entering the list page from the home page, the list page data will be reset and the new Data to achieve the effect of reloading the list page.

        However, this solution has a very bad point: if the list page is complex enough, there are pull-down refresh, pull-down loading, pop-up windows, carousel, etc., when clearing the cache, you need to reset a lot of data and status, and It is also possible to manually destroy and reload certain components, which increases complexity and is prone to bugs.

        Next, let me talk about the new implementation plan I thought of (the code is based on Vue3).

save flow

demo:  Jump Tips - Rare Earth Nuggets [1]

Code:  Jump Tips - Rare Earth Nuggets [2]

keep-alive cache and purge

keep-alive cache principle: when entering the page, the rendering of the page component is completed, and keep-alive will cache the instance of the page component; after leaving the page, the component instance will not be destroyed because it has been cached; when entering the page again, it will cache The component instance is taken out for rendering, because the component instance saves the data of the original page and the state of the Dom, then the original page can be obtained by directly rendering the component instance.

        The biggest problem of keep-alive is cache cleaning. If there is a simple cache cleaning method, then the keep-alive component will be very cool to use.

        However, the keep-alive component does not provide an API to clear the cache. Is there any other way to clear the cache? The answer is yes. Let's first look at the props of the keep-alive component:

include - string | RegExp | Array。只有名称匹配的组件会被缓存。
exclude - string | RegExp | Array。任何名称匹配的组件都不会被缓存。
max - number | string。最多可以缓存多少组件实例。

        From the include description, I found that include can be used to clear the cache. The method is: add the component name to include, and the component will be cached; remove the component name, and the component cache will be cleared. According to this principle, simply encapsulate the code with hook:

import { ref, nextTick } from 'vue'

const caches = ref<string[]>([])

export default function useRouteCache () {
  // 添加缓存的路由组件
  function addCache (componentName: string | string []) {
    if (Array.isArray(componentName)) {
      componentName.forEach(addCache)
      return
    }
    
    if (!componentName || caches.value.includes(componentName)) return

    caches.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
  }
}

The usage of hook is as follows:

<router-view v-slot="{ Component }">
  <keep-alive :include="caches">
    <component :is="Component" />
  </keep-alive>
</router-view>

<script setup lang="ts">
import useRouteCache from './hooks/useRouteCache'
const { caches, addCache } = useRouteCache()

<!-- 将列表页组件名称添加到需要缓存名单中 -->
addCache(['List'])
</script>

Clear the list page cache as follows:

import useRouteCache from '@/hooks/useRouteCache'

const { removeCacheEntry } = useRouteCache()
removeCacheEntry('List')

        The removeCacheEntry method here clears the instance of the list component, and the 'List' value is still in the include of the component. The next time you re-enter the list page, the list component will be reloaded, and the list component will continue to be cached afterwards.

When the list page clears the cache

Clear the cache after entering the list page

        In the beforeRouteEnter hook of the routing component of the list page, determine whether it is entered from another page (Home). If yes, clear the cache; if not, use the cache.

defineOptions({
  name: 'List1',
  beforeRouteEnter (to: RouteRecordNormalized, from: RouteRecordNormalized) {
    if (from.name === 'Home') {
      const { removeCacheEntry } = useRouteCache()
      removeCacheEntry('List1')
    }
  }
})

        This caching method has a not-so-friendly place: when entering the list page from the home page, switching back and forth between the list page and the details page, the list page is cached; but when switching between the home page and the list page with the forward and back of the browser, We more hope that the list page can retain the cache, just like the browser will cache the original page when moving forward and backward in multiple pages. But in fact, the list page is refreshed, which requires another solution, clearing the cache when the link is clicked .

Clear the cache before clicking the link

        Before clicking on the jump list page on the home page, clear the list page cache when the event is clicked. In this way, use the browser to switch back and forth between the home page and the list page. The list page is in the cache state, as long as the jump link is clicked again When the list page is reloaded, it meets expectations.

// 首页 Home.vue

<li>
  <router-link to="/list" @click="removeCacheBeforeEnter">列表页</router-link>
</li>


<script setup lang="ts">
import useRouteCache from '@/hooks/useRouteCache'

defineOptions({
  name: 'Home'
})

const { removeCacheEntry } = useRouteCache()

// 进入页面前,先清除缓存实例
function removeCacheBeforeEnter () {
  removeCacheEntry('List')
}
</script>

State management implements caching

Page caching can also be implemented by storing the state and data of the page through the state management library. Pinia is used for state management here.

First use pinia to create a list page store:

import { defineStore } from 'pinia'

interface 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

Then use the store in the list page:

<div>
  <el-page-header @back="goBack">
    <template #content>状态管理实现列表页缓存</template>
  </el-page-header>
  <el-table v-loading="loading" :data="tableData" border style="width: 100%; margin-top: 30px;">
    <el-table-column prop="id" label="id" />
    <el-table-column prop="content" label="内容"/>
    <el-table-column label="操作">
      <template v-slot="{ row }">
        <el-link type="primary" @click="gotoDetail(row)">进入详情</el-link>
        <el-tag type="success" v-if="row.id === listStore.curRow?.id">刚点击</el-tag>
      </template>
    </el-table-column>
  </el-table>
  <el-pagination
    v-model:currentPage="listStore.currentPage"
    :page-size="listStore.pageSize"
    layout="total, prev, pager, next"
    :total="listStore.list.length"
  />
</div>
  
<script setup lang="ts">
import useListStore from '@/store/listStore'
const listStore = useListStore()

...
</script>

        Use the beforeRouteEnter hook to judge whether it comes from the home page, and if so,  listStore.$reset() reset the data, otherwise use the cached data status; then  listStore.isRefresh judge whether to re-acquire the list data according to the mark.

defineOptions({
  beforeRouteEnter (to: RouteLocationNormalized, from: RouteLocationNormalized) {
    if (from.name === 'Home') {
      const listStore = useListStore()
      listStore.$reset()
    }
  }
})

onBeforeMount(() => {
  if (!listStore.useCache) {
    loading.value = true
    setTimeout(() => {
      listStore.setList(getData())
      loading.value = false
    }, 1000)
    listStore.useCache = true
  }
})

shortcoming

        If you use state management to do caching, you need to store the state data in the stroe. If there are more states, it will be a bit cumbersome, and writing the state in the store is definitely not as intuitive as writing it in the list component; because the state management is only for the list page For data caching, for some uncontrolled components, changes in the internal state of the component cannot be cached, which leads to a difference between the page rendering and the original one, requiring additional code operations.

Page pop-up window realizes caching

        Make the details page into a full-screen pop-up window, then enter the details page from the list page, simply open the details page pop-up window and cover the list page, so as to achieve the effect of "caching" the list page instead of real caching.

There is another problem here. After opening the details page, if you click back, you will return to the home page. In fact, we want to return to the list page. This requires adding a history record to the details pop-up window. For example, the address of the list page is '/list' , open the details page and change to '/list?id=1'.

Pop-up component implementation:

// PopupPage.vue

<template>
  <div class="popup-page" :class="[!dialogVisible && 'hidden']">
    <slot v-if="dialogVisible"></slot>
  </div>
</template>

<script setup lang="ts">
import { useLockscreen } from 'element-plus'
import { computed, defineProps, defineEmits } from 'vue'
import useHistoryPopup from './useHistoryPopup'

const 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 = computed<boolean>({ // 控制弹窗显示
  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')
})
</script>

<style lang='less'>
.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 window component call:

<popup-page 
  v-model="visible" 
  full
  :history="{ id: id }">
  <Detail></Detail>
</popup-page>

hook: useHistoryPopup Reference article: https://juejin.cn/post/7139941749174042660 [3]

shortcoming

        The pop-up window implements page caching, which has relatively large limitations and is only valid in the list page and detail page. After leaving the list page, the cache will become invalid, which is more suitable for some simple caching scenarios.

Parent-child routing implements caching

        The principle of this solution is actually a page pop-up window, the list page is the parent route, and the details page is the child route. When jumping from the list page to the details page, the details page sub-routes are displayed, and the details page is displayed in full screen, covering the list page.

Declare parent-child routes:

{
  path: '/list',
  name: 'list',
  component: () => import('./views/List.vue'),
  children: [
    {
      path: '/detail',
      name: 'detail',
      component: () => import('./views/Detail.vue'),
    }
  ]
}

List page code:

// 列表页
<template>
  <el-table v-loading="loading" :data="tableData" border style="width: 100%; margin-top: 30px;">
    <el-table-column prop="id" label="id" />
    <el-table-column prop="content" label="内容"/>
    <el-table-column label="操作">
      <template v-slot="{ row }">
        <el-link type="primary" @click="gotoDetail(row)">进入详情</el-link>
        <el-tag type="success" v-if="row.id === curRow?.id">刚点击</el-tag>
      </template>
    </el-table-column>
  </el-table>
  <el-pagination
    v-model:currentPage="currentPage"
    :page-size="pageSize"
    layout="total, prev, pager, next"
    :total="list.length"
  />
  
  <!-- 详情页 -->
  <router-view class="popyp-page"></router-view>
</template>

<style lang='less' scoped>
.popyp-page {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 100;
  background: #fff;
  overflow: auto;
}
</style>

Guess you like

Origin blog.csdn.net/lambert00001/article/details/131866072