How to Manage Cached Pages in Vue

  • 2021-10-25 05:59:35
  • OfStack

Directory Problem 1 Destroy 1. How to Destroy
2. When to destroy
2.1 Scenario 1 uses route. query to record the current page stack depth
2.2 Scenario 2 uses the Vue instance itself to record the current stack depth
2.3 Scenario 3 Using history. state Record Stack Depth
Question 2: Different parameters cache multiple instances on the same page. 1. How to achieve only 1
1.1 timestamp, super random number 1.2 route stack height + pathname 2. How to assign key to vnode of page
2.1 Dynamic binding of Key via route. query
2.2 Assign value directly by obtaining Vnode
Summarize

<keep-alive>
 <router-view />
</keep-alive>

Built in Vue < keep-alive > Components can help us to significantly improve the twice access speed of pages by caching all routing pages (of course, some pages can also be targeted) when developing SPA applications, but they also bring us troubles in some scenarios, including two main contradictions:

How the cache page is destroyed at the right time (keep-alive component provides three parameters to dynamically configure the cache state, but the effect is limited, which will be analyzed later) How to cache multiple different pages in the same path (different references on the same page), for example, Taobao product page continues to jump to another product page

This paper mainly focuses on these two problems, which are referred to by Question 1 and Question 2 later.

All pages in this article are keep-alive by default

Question 1 Destruction

As the business logic becomes complex, the routing stack gradually rises. In theory, users can route indefinitely. Inevitably, we need to manage the page data cached in memory. The page data includes two parts, Vue instance and corresponding Vnode. See src/core/components/keep-alive. js definition of cache in Vue source code


 this.cache = Object.create(null) // Used to cache vnode cache[key] => Vnode
 this.keys = [] // Used to record the cached vnode Adj. key

Vnode is not reused after caching, but only the Vue instance mounted on it.


if (cache[key]) {
 vnode.componentInstance = cache[key].componentInstance // Only from the cached vnode Get from vue Instance is attached to the new vnode Upper 
 // make current key freshest
 remove(keys, key)
 keys.push(key)
}

Why not? Because of BUG, the earliest version 1 implementation did use cached Vnode directly.

From src/core/components/keep-alive. js init version


export default {
 created () {
 this.cache = Object.create(null)
 },
 render () {
 const childNode = this.$slots.default[0]
 const cid = childNode.componentOptions.Ctor.cid
 if (this.cache[cid]) {
  const child = childNode.child = this.cache[cid].child // Directly get the cached vnode
  childNode.elm = this.$el = child.$el
 } else {
  this.cache[cid] = childNode
 }
 childNode.data.keepAlive = true
 return childNode
 },
 beforeDestroy () {
 for (const key in this.cache) {
  this.cache[key].child.$destroy()
 }
 }
}

What we need to manage is actually cache and keys. keep-alive provides three parameters to dynamically manage cache:


include -  Only components with matching names are cached. 
exclude -  Any components with matching names will not be cached. 
max -  How many component instances can be cached at most. 

Their function is very simple, and the source code is easy to read:

So when we want to manage these caches, the simple solution is to manipulate these three parameters, modify include and exclude to cache or clear some caches, but it should be noted that they match the component's name:

From src/core/components/keep-alive. js


const name: ?string = getComponentName(componentOptions)

Therefore, clearing the cache will clear all instances of a component indiscriminately, which obviously does not meet our needs.

The logic of max is to clear the cache at the bottom of the stack when the maximum value is exceeded,

From src/core/components/keep-alive. js:


if (this.max && keys.length > parseInt(this.max)) {
 pruneCacheEntry(cache, keys[0], keys, this._vnode)
}

We have to solve problem 1. The API provided by the government can't work. We can only come by ourselves. What we need is to solve two sub-problems:

When will it be destroyed How to destroy it

1. How to destroy

Let's see how to destroy it first, If it's easy to destroy an instance, You can use this. $destroy () directly, Is this all right, No, so the original vnode and key are still kept in the cache cache and keys. There will be problems when visiting again, vnode1 is kept straight, but its instance has been destroyed. At this time, an instance of vue will be created in the process of update of vue, that is to say, as long as a page of keep-alive has called this once. $destroy (), but the cache array has not been cleaned up, this page will definitely be re-created when it is re-rendered, of course, it will go through the whole life cycle again. Ultimately, the phenomenon is that this page seems to have not been cached.


this.$destroy(); // Not fit keep-alive Component 

So destruction requires both the cache cache and the cache keys to be cleaned, and the following defines a $keepAliveDestroy method to clear the cache at the same time:


 const dtmp = Vue.prototype.$destroy;
 const f = function() {
 if (this.$vnode && this.$vnode.data.keepAlive) {
  if (this.$vnode.parent && this.$vnode.parent.componentInstance && this.$vnode.parent.componentInstance.cache) {
  if (this.$vnode.componentOptions) {
   var key = !isDef(this.$vnode.key)
   ? this.$vnode.componentOptions.Ctor.cid + (this.$vnode.componentOptions.tag ? `::${this.$vnode.componentOptions.tag}` : '')
   : this.$vnode.key;
   var cache = this.$vnode.parent.componentInstance.cache;
   var keys = this.$vnode.parent.componentInstance.keys;
   if (cache[key]) {
   if (keys.length) {
    var index = keys.indexOf(key);
    if (index > -1) {
    keys.splice(index, 1);
    }
   }
   delete cache[key];
   }
  }
  }
 }
 dtmp.apply(this, arguments);
 }
 Vue.prototype.$keepAliveDestroy = f;

2. When to destroy

So when will it be destroyed? There are two trigger opportunities:

When replace, pages A--replace-- > Page B (clear page A) When route back, pages A--push-- > Page B--back-- > Page A (clear page B)

replace is relatively simple, we can directly intercept the replace method of router, in which the current page is cleared. (There are exceptions here, for example, when switching Tab, we will talk about it last.)

Let's take a look at route back specifically. If there is a return key on our page, it is a very correct time to clear the cache here, but we can't ignore the return key of the browser and the physical return key on Android. After taking this situation into account, the scheme of using only the return key can't be satisfied.

2.1 Scenario 1 uses route. query to record the current page stack depth

Each time push or replace is added one parameter on query to record the current depth


this.$router.push({
 path:"/targer",
 query:{
 stackLevel:Number(this.$route.query.stackLevel) + 1 	
 }
})

This scheme has obvious drawbacks. It is very ugly and dangerous to expose a parameter externally, and users can modify it at will. When promoting web pages, the promotion link copied by the business to the production environment may also carry a strange https://xxx.com/foo? bar=123 & stackLevel = 13 suffix. Abandon

2.2 Scenario 2 uses the Vue instance itself to record the current stack depth

After hack drops push and replace methods of router, You can mount 1 _ stackLevel on the vm of the target page every time you jump, This solves the problem of Scheme 1, which is not exposed to users, and is invisible and cannot be modified in URL. However, we can't ignore another devil in the browser-refresh key. URL will not change when refreshing, but vm instance needs to be recreated, so our stack depth mark will be lost. Abandon

2.3 Scenario 3 uses history. state record stack depth

Finally, it can be invisible to users and saved when refreshing. That's history. state, so what we need to do is to deeply save stack into history. state, which can completely save the whole routing chain.

When we get the target page stack depth less than the current page, we can destroy the current page.


 this.cache = Object.create(null) // Used to cache vnode cache[key] => Vnode
 this.keys = [] // Used to record the cached vnode Adj. key
0

Question 2. Multiple instances of cache with different parameters on the same page

You can see src/core/components/keep-alive. js in the source code


 this.cache = Object.create(null) // Used to cache vnode cache[key] => Vnode
 this.keys = [] // Used to record the cached vnode Adj. key
1

An vnode will use the component name if there is no key, so the key in the default cache is the component name. If the components are the same, we can solve this problem by having our own key on each page. How to realize that each page has its own key? There are two sub-problems:

How to be only 1 How to assign key to vnode of a page

1. How to be only 1

1.1 Time Stamps, Super Large Random Numbers

 this.cache = Object.create(null) // Used to cache vnode cache[key] => Vnode
 this.keys = [] // Used to record the cached vnode Adj. key
2

1.2 Route Stack Height + Path Name

key = vm._stack + router. currentRoute. path This scheme uses the current stack height + pathname. Why do you need a pathname? Because the stack height is unchanged when replace, only the pathname changes.

2. How to assign key to the page's vnode

There are currently two schemes to assign values to vue-router, the current Vnode, key:

2.1 Dynamic binding of Key via route. query

This scheme is relatively simple to implement


// Binding key
...
<router-view :key='$route.query.routerKey' />
...


//push Hour 
this.$router.push({
 path:"/foo",
 query:{
 	routerKey: Date.now() // Random key
 }
})

This method is very simple and effective to use, but the disadvantage is that it will also expose a strange parameter in URL

2.2 Assign value directly by getting Vnode

At which stage do you assign a value to key of Vnode? The answer is obvious. Before the render function of keep-alive component enters, src/core/components/keep-alive. js


 this.cache = Object.create(null) // Used to cache vnode cache[key] => Vnode
 this.keys = [] // Used to record the cached vnode Adj. key
4

We can drop the render function of keep-alive by hack, and then get the first child node in slot, assign its key, and then call render of keep-alive:


 this.cache = Object.create(null) // Used to cache vnode cache[key] => Vnode
 this.keys = [] // Used to record the cached vnode Adj. key
5

Summarize

Through the above analysis of the problems, we have solved the core problem of automatic cache management. This article is a summary of the open source library vue-router-keep-alive-helper, which is a simple and easy-to-use keep-alive cache automation management tool, and bid farewell to Vue cache management problems. If it is useful to you, thank you for your generosity Star.

Demonstration Demo Sample Code

Bilibili demo video thanks to 3 companies.

The above is how to manage the Vue cache pages in detail, more information about vue cache pages please pay attention to other related articles on this site!


Related articles: