introduce

Keep-alive is a built-in component of VUE. It caches inactive component instances rather than destroying them directly, and it is an abstract component that is not rendered into the real DOM and does not appear in the parent component chain. It provides include and exclude attributes that allow components to cache conditionally, where exclude takes precedence over include and Max can cache as many component instances as possible.

Keep-alive details document

Using keep-alive adds two hook functions, activated and deactivated

In the following article, I use keep-alive together with vue-router. Currently, keep-Alive can also cache individual components, so I won’t go into details here.

Include and exclude

Include and Exclude Prop allow components to cache conditionally. Both can represent detailed health documents with comma-delimited strings, regular expressions, or an array

I use vuEX here with array dynamic control

<keep-alive :include="includes" exclude="" :max="10" >
   <router-view/>
</keep-alive>

<script>
 export default {
	computed: {
	  includes() {
	     return state= > state.router.includes
	   }
	},
	methods: {
     changeStoreIncludes() {
       this.$store.commit('changeIncludes'.'tableLists'); }}}</script>  

// vuex
mutations: {
	changeIncludes(state, payload) {
	  state.includes = payload
	} 
}	
Copy the code

Include and exclude are invalid

To use the include/exclude attribute, you need to assign all vue class names (not route names). Otherwise, include/exclude does not take effect

export default {
 name:'TableList'.// Include or exclude specifies the name to be used
 data () {
  return{}}},Copy the code

Use v-if directly to differentiate

<transition enter-active-class="animated zoomInLeft" leave-active-class="animated zoomOutRight">
     <keep-alive>
          <router-view v-if="$route.meta.keepAlive">
          </router-view>
      </keep-alive>
</transition>
<transition enter-active-class="animated zoomInLeft" leave-active-class="animated zoomOutRight">
     <router-view v-if=! "" $route.meta.keepAlive">
       </router-view>
</transition>
Copy the code

Do so much more simple and clear, and cooperate with animation works better, no longer vue component declarations inside the name, but in the route of meta add {keepAlive: true} fields, if routing control is the background, the front-end debugging is chicken ribs.

Exposed problem

Question 1:

Public location problem. When the current list page jumps to the details page, the route back to the list will also appear public location. (If you use the browser’s fallback mode, the location is not common.)

For this position of the public situation, I am confused, look forward to big guy answer 🤝, there are a few points to say I do not know right or wrong, to be verified.

  • Multiple page
  1. If there is a data request, the browser will put the page at the top, right?
  2. If it’s a static page, the browser will scroll to where you were before, right?
  3. The above is just the jump behavior of the browser in use. If you use href or route to encapsulate some methods, then all of them will be at the top. Okay?
  • Verify above 🤝
  1. Based on SPA model development, so there is only one page, the page switching is using hash mapping relationship with the component, the vue – is the router through the hash to simulate the full url, but is still a url for the page, so in any component scrolling page, switch to the other components, page remain before rolling state, That’s what happens when places are common.

Question 2:

Add three pages A, B and C, I now only want A->B when A cache, then B->A, show the cache page, C->A, A->B->C->A and so on do not cache.

scrollBehavior

My idea for question 1 was to cache the scrolling height directly before a jump and then reassign the height to it every time I came back, but it would be a bit troublesome if I had to use the cache if I had too many pages, and then I found that routing provided a way to do this.

Description of scrollBehavior

The scrollBehavior method receives to and FROM routing objects. The third parameter savedPosition is available if and only if popState navigation (triggered by the browser’s forward/back buttons).

const router = new VueRouter({
  mode: 'hash',
  routes,
  scrollBehavior(to, from, savedPosition) {
    // This method checks document.body by default. If you have a custom scroll box, there is no way to control your scroll height
    console.log(savedPosition);
    // If a falsy(not false) or an empty object is returned, no scrolling will occur. In other words, this method is useless and will not be at the top of the page
    https://developer.mozilla.org/zh-CN/docs/Glossary/Falsy / / falsy document
    if (savedPosition) {
      return savedPosition
    } else {
      if (from.meta.keepAlive) {
        // This is not accurate, maybe my page scroll box is not the body,vue should have a setting place
        from.meta.scrollTop = document.documentElement.scrollTop;
      }
      return {x: 0.y: to.meta.scrollTop || 0}}}});export default router

// The new version supports asynchronous scrolling, which returns a Promise. This is particularly useful because the previous approach would not be able to override asynchronous requests on the page
scrollBehavior(to, from, savedPosition) {
  if (savedPosition) {
    return new Promise((resolve, reject) = > {
      setTimeout(() = > {
        resolve(savedPosition)
      }, 20)})}else {
    if (from.meta.keepAlive) {
      from.meta.scrollTop = document.documentElement.scrollTop;
    }
    return {x: 0.y: to.meta.scrollTop || 0}}}Copy the code

Achieve return not refresh, other menu into refresh

Implementation Method 1

// app.vue
<keep-alive>
   <router-view v-if="$route.meta.keepAlive" />
</keep-alive>

<router-view v-if=! "" $route.meta.keepAlive" />
Copy the code
// router.js
{
   path: '/table-list'.name: 'table-list'.component: TableList,
   meta: {keepAlive: true} // Add this
 },
 {
   path: '/table-detail'.name: 'table-detail'.component: () = > import('.. /views/table-detail.vue'),}Copy the code

Both approaches one and two are based on the two code snippets above.

Add the following code to the list page you want to cache

activated() {
  // If it is the first time to enter, do not execute the following function.
  if (this.hasFirst) return
  this.queryList()
},
beforeRouteLeave(to, from, next) {
  if (to.name === 'table-detail') {
    if (!from.meta.keepAlive) {
      from.meta.keepAlive = true; }}else {
    from.meta.keepAlive = false;
    this.$destroy(); // Destroy the instance of the class page table page, with a pit
  }
  next()
}
Copy the code

After completing the above code, A->B->A is fine, and then when A->C->A->B->A finds that list page A is no longer cached, it is A new page every time. Google’s approach is to force a cached page refresh if it is not the first time you enter. This.$destroy() can no longer cache the component after calling distory and will repeatedly generate multiple virtual DOM after entering the page.

To continue, use router.aftereach ((to,from)=>{}) in main.js

// If the page does not come from the detail page, it must be refreshed, otherwise it cannot be cached
router.afterEach((to, from) = > {
  // If the current page is refreshed or the details come in, the following code is executed to prevent the cache from being unable to be cached by executing the destruction method
  if (from.name && from.name ! = ='table-detail' && to.name === 'table-list') {
    let isRefresh = sessionStorage.getItem('isRefresh')
    if (isRefresh === '0') {
      setTimeout(() = > {// It must be asynchronous, or it cannot jump
        window.location.reload()
      })
      sessionStorage.setItem('isRefresh'.null)}else {
      sessionStorage.setItem('isRefresh'.'0')}}else if (from.name === 'table-list' && to.name === 'table-detail') {
    sessionStorage.setItem('isRefresh'.null)}else {
    sessionStorage.setItem('isRefresh'.'0')}})Copy the code

I don’t know why all the solutions from Google are refreshed in the details page. The problem is that there is no caching function when the user jumps to the details page for the first time and then returns to the list page. The second time will be normal, but the customer is likely to perform this operation.

This solution is primitive, the user experience is poor, and the need to cache multiple pages is difficult to control, so it is not recommended

Implementation Method 2

Inspired by method one, I can simulate a page refresh in other ways.

<template> <! -- Be sure to use v- hereifThe upside is that you can get a better experience with $nextTick. The flip side is that after using V-show, it basically hides the page, but if there's some DOM in there that doesn't diff, it shows up, and the simulated refresh experience isn't very good. For example, use input-><div v-if="isRouterAlive">
        <div>{{ddd}}</div>
        <input v-model="ddd" type="text" />
        <table-list ref="table" :multiple="true" :otherTableParams="otherTableParams" :tableColumn="column"/>
    </div>
</template>
<script>
  export default {
    // Other codes........
    activated() {
      if (this.$route.meta.isRefresh) { // If not jump to the details page
        const resetData = this.$options.data() // Get the data from the original data
        delete resetData.column  JumpEdit {cb: this.undefined} {cb: this.undefined} {cb: this

        Object.assign(this.$data, resetData) / / reset the data
        this.isRouterAlive = false // Do not display the current page through v-if
        this.$nextTick(function () { 
          window.scroll(0.0) // page top, do not use the timer below, there is a sense of frustration
          this.isRouterAlive = true // Use v-if to display the current page
        })
        setTimeout(() = > {
          this.queryList() // Get data asynchronously, related to my project component, you can get data directly above OK})}},beforeRouteLeave(to, from, next) {
      // If the page is not loaded into the detail page, display true to indicate that the page is reloaded
      from.meta.isRefresh = to.name ! = ='table-detail';
      next() // There is no jump without adding a route}}</script>
Copy the code

To optimize the

There are two problems with the current code: one is that from the detail page to the list page, the data doesn’t update, and if I change something in the detail page, then it will lag when I get to the list page; Second, jump from the details page to another list page and then jump to the cached list page, then it will cache the previous data, does not update the current page, optimize as follows:

activated() {
 // If the following method is not executed for the first time, the data will be requested twice
 // this. HasFirst generates a variable instead of a reactive one
 if (this.hasFirst) return
 if (this.$route.meta.isRefresh) {
    const data = this.$options.data()
    delete data.column

    Object.assign(this.$data, data)
    this.isRouterAlive = false
    this.$nextTick(function () {
      window.scroll(0.0)
      this.isRouterAlive = true
    })
    setTimeout(() = > {
      this.queryList()
    })
  } else if (this.$route.meta.isRefresh === false) { 
    // this.$route.meta. IsRefresh = {this.$route.meta. IsRefresh = {this
    this.queryList()
  }
}

beforeRouteEnter(to, from, next) {
 // This route guard function is executed first
 to.meta.isRefresh = from.name && from.name ! = ='table-detail';
  next()
},
// The following code can be annotated
// beforeRouteLeave(to, from, next) {
// from.meta.isRefresh = to.name ! == 'table-detail';
// next()
// }
Copy the code

Finally, you can extract the code into a mixins and write a refreshed component that can be used and removed, or read this article.

Implementation Method 3

The include and exclude provided by keep-alive are then dynamically controlled with VUEX.

Route entry page

// app.vue
<keep-alive :include='includes' :exclude=' ':max="3">
  <router-view></router-view>
</keep-alive>
Copy the code

Include, exclude, and Max represent the maximum number of caches.

// Get vuex data
import {mapGetters} from 'vuex'
export default {
  computed: {// Dynamic monitoring in computed. mapGetters(['includes']),},methods: {
     changeStore() {
       // Change vUE data, which is not used here
       this.$store.commit('change'.'tableLists')}}}Copy the code

Vuex

const keepalive = {
  state: {
    includes: ['tableLists']},mutations: {
    change(state, payload) {
      state.includes = payload
    },
  },
  getters: {
    includes(state) {
      return state.includes
    }
  }
};

export default keepalive
Copy the code

Partial code for the list page

activated() {
  // Same as above, do not execute the following method if it is first entered
  if (this.hasFirst) return
  this.queryList()
},
beforeRouteEnter(to, from, next) {
  / / this time there is not this, so here is not operating vuex with this words, I am in the main, js assigned to the window
  window._store.commit('change'['tableLists']);
  next()
  // You can also use it this way
  // next((el) => {
  // el.$store.commit('change', ['tableLists']);
  // })
},
beforeRouteLeave(to, from, next) {
  if(to.name ! = ='table-detail') {
    // If not jump to details page, pass an empty array, this cannot use "" default is all cache
    this.$store.commit('change'[]); } next() }Copy the code

Details page part of the code

beforeRouteLeave(to, from, next) {
  if(to.name ! = ='table-list') {
    this.$store.commit('change'[]); } next() }Copy the code

Routing page

Since includes does not define keepalive in the route, the scrollBehavior method above requires extra handling when using a composite event jump

scrollBehavior(to, from, savedPosition) {
  if (savedPosition) {
    return new Promise((resolve) = > {
      setTimeout(() = > {
        resolve(savedPosition)
      }, 20)})}else {
    const ary = ['Invest'.'Store'];  // This is the route name of the page to be cached, not the name of the vue class

    if (ary.includes(from.name)) {
      / * a recent review time Found a problem * if the list page jump to the details page, when this time is already in the details page, * if when the height of the current page details page didn't list page jump came in the height of the rolling high, this time will get no real page height, * The solution is to get the height of the page before each page leaves and save it in the meta. This can solve this problem and also solve the problem of not getting the height of the page when it is not the body scrolling. * /
      from.meta.scrollTop = document.documentElement.scrollTop;
    }
    return {x: 0.y: to.meta.scrollTop || 0}}}Copy the code

The above code is trivial and needs to be added to every page, so in the actual project you can add a Keepalive mixins for easy management.

Note that using include and exclude

  1. Add {name:xx} inside each component
  2. If include is set to empty, every page will be cached
  3. Exclude takes precedence over include

It is the first time to publish an article in my life. I hope you can give me more advice.


Reference documentation

keep-alive

vueRouterIssues

scrollBehavior

Navigation guard