demand

During the project to develop VUE, a requirement was encountered: A video list page, showing name and whether video collection, click go in one watch, can collect or cancel collect, return the need to remember the page, list page, at the same time the state of the video collection also need to update, but from other pages in video list page not cache the page, This is the first page of the video list page

PageAList ->pageADetail->pageAList, cache pageAList, at the same time the video’s collection status needs to be updated if there is any change, other pages ->pageAList, pageAList does not cache

I found a lot of other people’s methods on the Internet, which didn’t meet our needs

Then several people in our team worked on it for a few days, and really came up with a set of methods to achieve this demand

The effect after implementation

No figure no truth, use a GIF figure to see the effect of the realization!!

Operation process:

  • Home page ->pageAList, jump to the second page -> Home page ->pageAList, the page number of the first page is displayed, indicating that the page of pageAList is not cached
  • PageAList, jump to the third page, click video 22 -> enter the video details page pageADetail, click “Collect”, collect successfully, click “Return” -> pageAList, the third page is displayed, and the “collect” status of video 22 has changed from “never collect” to “collected”. Enter pageAList from pageADetail. The pageAList page is cached and its status is updated

Description:

  • Level 2 cache: cache A from A->B->A
  • Level 3 cache: A->B->C->B->A, CACHE A, B because most of the project is level 2 cache, here we do level 2 cache, but this does not mean that my cache method is not suitable for level 3 cache, I will also talk about how to implement level 3 cache later

Implementing level 2 caching

With vuE-CLI2 scaffolding built a project, using this project to illustrate how to achieve first look at the project catalog

1. Prerequisites

  • The project introduces vUE, Vuex, VUE-Router, Axios and other VUE family buckets
  • I introduced element-UI just for the sake of aesthetics, after all, I was terminally lazy and didn’t want to write my own style
  • Configure the front-end proxy in config/index.js
  • Introduce Express, start the background, open port 3003 at the back end, and provide API support for the front end. Let’s take a look at the server code server/app.js. It is very simple, that is, 30 pieces of data were created, 3 interfaces were written, and dozens of lines of files were directly built to build a Node server, which solved the data simulation problem simply and rough. You can mock, you can mock
const express = require('express')
// const bodyParser = require('body-parser')
const app = express()
let allList = Array.from({length: 30}, (v, i) => ({
  id: i,
  name: 'video' + i,
  isCollect: false
}))
// Background Settings allow cross-domain access
// Both the front and back ends are local localhosts, so you do not need to set cORS across domains. If the cORS is deployed on a server, you need to set CORS across domains
// app.all('*', function (req, res, next) {
// res.header('Access-Control-Allow-Origin', '*')
// res.header('Access-Control-Allow-Headers', 'X-Requested-With')
// res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS')
/ / res. The header (' X - Powered - By ', '3.2.1)
// res.header('Content-Type', 'application/json; charset=utf-8')
// next()
// })
app.use(express.json())
app.use(express.urlencoded({extended: false}))
// 1 Get the list of all videos
app.get('/api/getVideoList'.function (req, res) {
  let query = req.query
  let currentPage = query.currentPage
  let pageSize = query.pageSize
  let list = allList.slice((currentPage - 1) * pageSize, currentPage * pageSize)
  res.json({
    code: 0.data: {
      list,
      total: allList.length
    }
  })
})
// 2 Obtain the details of a video
app.get('/api/getVideoDetail/:id'.function (req, res) {
  let id = Number(req.params.id)
  let info = allList.find(v= > v.id === id)
  res.json({
    code: 0.data: info
  })
})
// 3 Favorites or unfavorites video
app.post('/api/collectVideo'.function (req, res) {
  let id = Number(req.body.id)
  let isCollect = req.body.isCollect
  allList = allList.map((v, i) = > {
    returnv.id === id ? {... v, isCollect} : v }) res.json({code: 0})})const PORT = 3003
app.listen(PORT, function () {
  console.log('app is listening port' + PORT)
})

Copy the code

2. Configure routes

In the route configuration, add the keepAlive attribute to the meta of the route to be cached, and set the value to true. This is known as the cache route component. In our project, the route to be cached is pageAList. The route file SRC /router/index.js is as follows:

import Vue from 'vue'
import Router from 'vue-router'
import home from '.. /pages/home'
import pageAList from '.. /pages/pageAList'
import pageADetail from '.. /pages/pageADetail'
import pageB from '.. /pages/pageB'
import main from '.. /pages/main'
Vue.use(Router)

export default new Router({
  routes: [{path: '/'.name: 'main'.component: main,
      redirect: '/home'.children: [{path: 'home'.name: 'home'.component: home
        },
        {
          path: 'pageAList'.name: 'pageAList'.component: pageAList,
          meta: {
            keepAlive: true}}, {path: 'pageB'.component: pageB
        }
      ]
    },
    {
      path: '/pageADetail'.name: 'pageADetail'.component: pageADetail
    }
  ]
})

Copy the code

3. Vuex configuration

Vuex stores an array called excludeComponents which operates on components that need to be cached

state.js

const state = {
  excludeComponents: []}export default state
Copy the code

Also add two methods to this.js. AddExcludeComponent adds elements to excludeComponents, and removeExcludeComponent removes elements from the excludeComponents array

Note: The second argument to both methods is an array or component name

mutations.js

const mutations = {
  addExcludeComponent (state, excludeComponent) {
    let excludeComponents = state.excludeComponents
    if (Array.isArray(excludeComponent)) {
      state.excludeComponents = [...new Set([...excludeComponents, ...excludeComponent])]
    } else {
      state.excludeComponents = [...new Set([...excludeComponents, excludeComponent])]
    }
  },
  // excludeComponent can be a component name string or array
  removeExcludeComponent (state, excludeComponent) {
    let excludeComponents = state.excludeComponents
    if (Array.isArray(excludeComponent)) {
      for (let i = 0; i < excludeComponent.length; i++) {
        let index = excludeComponents.findIndex(v= > v === excludeComponent[i])
        if (index > - 1) {
          excludeComponents.splice(index, 1)}}}else {
      for (let i = 0, len = excludeComponents.length; i < len; i++) {
        if (excludeComponents[i] === excludeComponent) {
          excludeComponents.splice(i, 1)
          break
        }
      }
    }
    state.excludeComponents = excludeComponents
  }
}
export default mutations

Copy the code

4. Keep-alive package router-view

Wrap app. vue’s router-view with keep-alive, and the route to main.vue needs to be wrapped in the same way. This is important because pageAList components are matched from their router-view



Vuex addExcludeComponent — vuex addExcludeComponent — vuex addExcludeComponent — Vuex addExcludeComponent — Vuex addExcludeComponent — Vuex addExcludeComponent — Vuex addExcludeComponent AddExcludeComponent stands for do not cache components. This is a little convoluted, so remember this rule so you don’t get convoluted later.

App.vue

<template>
  <div id="app">
    <keep-alive :exclude="excludeComponents">
      <router-view v-if="$route.meta.keepAlive"></router-view>
    </keep-alive>
    <router-view v-if=! "" $route.meta.keepAlive"></router-view>
  </div>
</template>

<script>
export default {
  name: 'App',
  computed: {
    excludeComponents () {
      return this.$store.state.excludeComponents
    }
  }
}
</script
Copy the code

main.vue

<template>
  <div>
    <ul>
      <li v-for="nav in navs" :key="nav.name">
        <router-link :to="nav.name">{{nav.title}}</router-link>
      </li>
    </ul>
    <keep-alive :exclude="excludeComponents">
      <router-view v-if="$route.meta.keepAlive"></router-view>
    </keep-alive>
    <router-view v-if=! "" $route.meta.keepAlive"></router-view>
  </div>
</template>
<script>
export default {
  name: 'main.vue',
  data () {
    return {
      navs: [{
        name: 'home'.title: 'home'
      }, {
        name: 'pageAList'.title: 'pageAList'
      }, {
        name: 'pageB'.title: 'pageB'}}},methods: {},computed: {
    excludeComponents () {
      return this.$store.state.excludeComponents
    }
  },
  created () {
  }
}
</script>
Copy the code

The next two Settings are important

5. Level-1 components

Add beforeRouteEnter and beforeRouteLeave for the level 1 route pageAList that needs to be cached

import {getVideoList} from '.. /api'
export default {
  name: 'pageAList'.// The component name does not need to be the same as the route name of the component
  data () {
    return {
      currentPage: 1.pageSize: 10.total: 0.allList: [].list: []}},methods: {
    getVideoList () {
      let params = {currentPage: this.currentPage, pageSize: this.pageSize}
      getVideoList(params).then(r= > {
        if (r.code === 0) {
          this.list = r.data.list
          this.total = r.data.total
        }
      })
    },
    goIntoVideo (item) {
      this.$router.push({name: 'pageADetail'.query: {id: item.id}})
    },
    handleCurrentPage (val) {
      this.currentPage = val
      this.getVideoList()
    }
  },
  beforeRouteEnter (to, from, next) {
    next(vm= > {
      vm.$store.commit('removeExcludeComponent'.'pageAList')
      next()
    })
    },
  beforeRouteLeave (to, from, next) {
    let reg = /pageADetail/
    if (reg.test(to.name)) {
      this.$store.commit('removeExcludeComponent'.'pageAList')}else {
      this.$store.commit('addExcludeComponent'.'pageAList')
    }
    next()
  },
  activated () {
    this.getVideoList()
  },
  mounted () {
    this.getVideoList()
  }
}
Copy the code
  • Before entering the pageAList component, excludeComponents removes the current component, that is, caches the current component, so any route to this component that is actually cached triggers the Activated hook
  • BeforeRouteLeave: ExcludeComponents If you leave the current page and jump to pageADetail, you need to remove the current component pageAList at excludeComponents, that is, cache the current component, and add pageAList to excludeComponents if you jump to another page. That is, the current component is not cached
  • GetVideoList is a mounted or created hook that retrieves data. If a secondary route changes data and a primary route needs to update, the value of Activated hook is used to retrieve data

6. Secondary components

Add the beforeRouteLeave route lifecycle hook for the secondary route component pageADetail that needs to cache the primary route

In the beforeRouteLeave hook, you need to clear the cache state of the tier 1 component first, and then cache the tier 1 component if the jump route matches the tier 1 component

beforeRouteLeave (to, from, next) {
    let componentName = ' '
    // When leaving the detail page, add pageAList to exludeComponents
    let list = ['pageAList']
    this.$store.commit('addExcludeComponent', list)
    // Cache mapping of component routing names to component names
    let map = new Map([['pageAList'.'pageAList']])
    componentName = map.get(to.name) || ' '
    // Remove pageAList from exludeComponents, i.e. cache pageAList
    this.$store.commit('removeExcludeComponent', componentName)
    next()
  }
Copy the code

7. Summary of implementation methods

  • After entering pageAList and caching it in beforeRouteEnter, there are two cases when leaving the current component:
    • PageADetail = pageADetail; pageADetail = pageADetail; pageADetail = pageADetail
      • If pageAList is cached in the beforeRouteLeave hook of pageADetail, then pageAList can be cached in the beforeRouteLeave hook of pageADetail. It’s the same page number as before
      • [pageADetail] [beforeRouteLeave] [pageADetail] [beforeRouteLeave] [pageADetail] [beforeRouteLeave] [pageADetail
    • 2 Go to the page that is not pageADetail and clear the pageAList cache in the beforeRouteLeave hook of pageAList

Scheme evaluation

I think this scheme is used to achieve the cache, the final effect is very perfect disadvantages:

  1. A bit too much code, cache code is not reusable
  2. Performance issues: If the value of activated hook is inserted into a tier 1 component, the value of activated hook will send two interface requests when a tier 1 component is inserted into a tier 1 component. There’s a little bit of impotence here

Program source code

Project source github address, welcome everyone to clone download

Project launch and effect demonstration

  1. npm installInstallation project dependencies
  2. npm run serverStart the background server to listen on local port 3003
  3. npm run devStart front-end projects

Three levels of cache

Now, let’s say I have three pages, A1-A2-A3, step by step, and I want to cache A2 when I return from A3 to A2, cache A1 when I return from A2 to A1, and I want to cache A1, so you can do it yourself, I won’t write it here, In fact, is the above idea, for everyone to study, you can pay attention to my wechat public number, there are three levels of cache code answer.

I’m sorry, still can’t avoid custom, no matter how you, I still want to give me the number of public advertising, the name is very common, front research center, but the content is not common, not regularly updated high-quality content of the front end: original translation or foreign excellent tutorial, the following is a public issue of qr code, welcome to sweep the yard to join, learn and progress together.

Recent quality articles

  • Advance to vUE with Yu Creek
  • Advanced with Yu Stream VUE (2)