Background: In the B-side system, in order to facilitate the use of the page design, we will add the TAB page similar to the browser TAB page function, in order to use the experience more similar to the browser TAB page, we need to cache the route. This article describes how the Vue project uses keep-Alive to dynamically cache tabs for different business scenarios.

About the keep alive

Keep-alive is an abstract component that does not parent child components and is not rendered as a node on the page.

This concept is not mentioned in the documentation of abstract component Vue. It has an abstract property of true. During the life cycle of the abstract component, we can intercept the events that the wrapped child component listens to, and we can also perform Dom operations on the child component to encapsulate the functionality we need. You don’t have to worry about the implementation of the child component. Besides kepp-alive, there are <transition><transition-group> and so on.

role

  • The ability to keep state in memory during component switching prevents repeated DOM rendering.
  • Avoid repeated rendering affects page performance, but also can greatly reduce interface requests, reduce server pressure.
  • Route caching and component caching are available.

Activated

If the value of activated is added to keep-alive mode, that is, the value of activated is activated periodically.

  • The first time the page is entered, the hook firing order

Created -> Mounted -> activated when you exit, only Activated when you enter again (forward or backward).

  • Mounted events that are executed only once are placed in Mounted. The method that the component executes each time it goes in is placed in activated.

Keep alive – parsing

Apply colours to a drawing

Keep-alive is the result of rendering determined by the function render, at the beginning will get slot child elements, call getFirstComponentChild to get the first child VNode element.

const slot = this.$slots.default
const vnode: VNode = getFirstComponentChild(slot)
Copy the code

Then determine whether the current component meets the caching conditions. If the component name does not match include or exclude, it will directly exit and return VNode without caching mechanism.

// check pattern const name: ? string = getComponentName(componentOptions) const { include, exclude } = this if ( // not included (include && (! name || ! matches(include, name))) || // excluded (exclude && name && matches(exclude, name)) ) { return vnode }Copy the code

The matching condition passes into the logic of the caching mechanism. If it hits the cache, the cache instance is set to the current component from the cache, and the key position is adjusted to put it last (LRU policy). If the cache is dead, the current VNode is cached and added to the key of the current component. If the number of cached components exceeds the value of Max, that is, there is not enough cache space, then pruneCacheEntry is called to remove the oldest component from the cache, that is, the component of keys[0]. The keepAlive of the component is then marked true, indicating that it is a cached component.

LRU cache strategy: Find the longest unused data in memory and replace it with new data. The algorithm culls data based on its historical access record. The core idea is that if the data has been accessed recently, it has a higher chance of being accessed in the future.

const { cache, keys } = this const key: ? string = vnode.key == null // same constructor may get registered as different local components // so cid alone is not enough (#3269) ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') : vnode.key if (cache[key]) { vnode.componentInstance = cache[key].componentInstance // make current key freshest remove(keys, key) keys.push(key) } else { cache[key] = vnode keys.push(key) // prune oldest entry if (this.max && keys.length > parseInt(this.max)) { pruneCacheEntry(cache, keys[0], keys, this._vnode) } }Copy the code

PruneCacheEntry is responsible for removing the component from the cache, and it calls the component $destroy method to destroy the component instance, empty the cache component, and remove the corresponding key.

function pruneCacheEntry ( cache: VNodeCache, key: string, keys: Array<string>, current? : VNode ) { const cached = cache[key] if (cached && (! current || cached.tag ! == current.tag)) { cached.componentInstance.$destroy() } cache[key] = null remove(keys, key) }Copy the code

Render summary

  • Through getFirstComponentChild access to the first child components, obtain the name of the component;
  • Match the include and exclude attributes to determine whether the current component is to be cached. If the match is successful, check whether the current component is cached.
  • If the key is hit in the cache, it is obtained directly and the key position is updated at the same time.
  • If the number of instances in the cache is not hit, it is set to the cache. At the same time, check whether the number of instances in the cache exceeds Max. If the number exceeds Max, it is deleted according to the LRU policy.
  • If there are changes to include and exclude, watch to listen for them and call pruneCache to modify the cache data when they change.

Implementation scheme based on keep-Alive cache

Option 1: Entire page caching

A keepAlive field is added to the meta attribute of the router, and then the parent or root component uses the keepAlive tag to cache the route based on the keepAlive field status:

<keep-alive> <router-view v-if="$route.meta.keepAlive" /> </keep-alive> <router-view v-if="! $route.meta.keepAlive" />Copy the code

Solution 2: Dynamic component caching

Use VUex with exclude and include to determine which components to cache. Note that this is a component, and that the cachedView array holds the component name, as follows:

<keep-alive :include="$store.state.keepAlive.cachedView">
    <router-view></router-view>
</keep-alive>
Copy the code

Scenario analysis

In the SPA application, users want to switch back and forth between Tab pages without losing the query results, and then clear the cache after closing.

The diagram below:

The expectation is cached when the user switches between tabs, and not when the user closes the Tab and opens it again from the left menu.

Routing cache Scheme

When the user closes the page and opens the menu from the left, the page is cached, which is not normal. Therefore, to resolve the freshness problem, you can trigger the query request from Activated to ensure the freshness of the data.

activated(){
 getData()
}
Copy the code

But after using it, I found that because you always request data every time you switch Tab, but if the project has a large amount of data, frequent requests will cause a lot of pressure on the back end.

Dynamic component caching scheme

Version 1 requires frequent data drags that make this scheme unsuitable. Only dynamic cache component schemes are available.

<keep-alive :include="cachedViews">
  <router-view :key="key"></router-view>
</keep-alive>
Copy the code

CachedViews dynamically adds and deletes an array of component names (so component names don’t have to be repeated) to be cached by listening to routes:

const state = {
  cachedViews: [],
}
const mutations = {
  ADD_VIEWS: (state, view) => {
    if (state.cachedViews.includes(view.name)) return
    state.cachedViews.push(view.name)
  },
  DEL_CACHED_VIEW: (state, view) => {
    const index = state.cachedViews.indexOf(view.name)
    index > -1 && state.cachedViews.splice(index, 1)
  },
}
const actions = {
  addCachedView({ commit }, view) {
    commit('ADD_VIEWS', view)
  },
  deleteCachedView({ commit }, view) {
    commit('DEL_CACHED_VIEW', view)
  },
}
export default {
  namespaced: true,
  state,
  mutations,
  actions,
}
Copy the code

By listening for route changes:

watch: { '$route'(newRoute) { const { name } = newRoute const cacheRout = this.ISCACHE_MAP[name] || [] cacheRout.map((item) => { store.dispatch('cached/addCachedView', { name: Item})})},}, and clear the component name when the page is closed via Tab:  closeTag(newRoute) { const { name } = newRoute const cacheRout = this.ISCACHE_MAP[name] || [] cacheRout.map((item) => {  store.dispatch('cached/deleteCachedView', { name: item }) }) }Copy the code

However, the problem of cache data invalidation will occur when switching Tab in router-view with different levels when encountering the nested rules, and the components cannot be cached. The nested rules are as follows:

How to solve it?

  • Solution 1: The menus are nested, but the routes are not nested

By maintaining two sets of data, one set nested to the left menu, the other set of flattened registered routes, the modified routes:

  • Solution 2: modify keep-alive to make the catch object global

Keep-alive is a cache object of the keep-Alive component. If it can be found in the cache object, render the vnode directly. So by putting this cache object in the global (global variable or vuex), I can cache ParnetView’s child components without caching it.

Import Vue from 'Vue' const cache = {} const keys = [] export const removeCacheByName = (name) => {/* remove code */} export default Object.assign({}, Vue.options.components.KeepAlive, { name: 'NewKeepAlive', created() { this.cache = cache this.keys = keys }, destroyed() {}, })Copy the code
  • Solution 3: Modify the keep-alive cache based on the route name

GetComponentName (componentName) keep-alive = componentName (componentName) keep-alive = componentName (componentName); Component name gets the mapping that is changed to the route name to make it cached only with the route name value.

function getComponentName(opts) {
  return this.$route.name
}
Copy the code

The cache key is also changed to the route name.

Refer to the link

  • Cn.vuejs.org/v2/api/#kee…
  • Cn.vuejs.org/v2/guide/co…
  • Juejin. Cn/post / 684490…

Article | chuai slanting

Focus on object technology, hand in hand to the cloud of technology