preface

In a VUE project, some components or pages do not need to be rendered more than once, so some components need to be conditionally persisted in memory. However, this is not simple data persistence, but the entire component (including data and view) persistence. It just so happens that VUE provides a built-in

component to do this.

when wrapping dynamic components, inactive component instances are cached rather than destroyed. Like
,

is an abstract component: it does not render a DOM element on its own, nor does it appear in the component’s parent chain. When a component is switched within

, its activated and deactivated lifecycle hook functions are executed accordingly.



The basic use

It is used in two versions. Prior to VUE 2.1.0, it was mostly implemented like this:

<keep-alive> <router-view v-if="$route.meta.keepAlive"></router-view> </keep-alive> <router-view v-if="! $route.meta.keepAlive"></router-view> new Router({ routes: [ { name: 'a', path: '/a', component: A, meta: { keepAlive: true } }, { name: 'b', path: '/b', component: B } ] })Copy the code

After the route meta is configured, route A’s $route.meta. KeepAlive is true and route B’s is false. So true will be wrapped in keep-alive and false in the outer layer. In this way, route A is cached. If there are other routes to be cached, simply add keepAlive: true to the route element.

After vue version 2.1.0, keep-alive added two new attributes: include(include components are cached) and exclude(exclude components are not cached and have a higher priority than include). Include and exclude attributes allow components to cache conditionally. Both can be represented as comma-delimited strings, regular expressions, or an array. Always use V-bind when using re’s or arrays.

Simple use:

<! -- comma-separated string --> <keep-alive include="a, B ">< Component :is="view"></component> </keep-alive> <! - regular expressions (using ` v - bind `) - > < keep alive: - include = "/ a | b/" > < component: is =" view "> < / component > < / keep alive - > <! - array (using ` v - bind `) - > < keep alive: - include = "(' a ', 'b')" > < component: is = "view" > < / component > < / keep alive - > <! -- <template> <div id="app"> < Transition :name="routerTransition"> <keep-alive :include="keepAliveComponentsData"> <router-view :key="$route.fullPath"></router-view> </keep-alive> </transition> </div> </template>Copy the code

It is recommended to use versions later than 2.1.0 for caching strategy, with much cleaner code and much less repetitive rendering.

Advanced advanced use

We understand the basic usage, but it is not as simple as imagined in daily projects, so we need to design the cache strategy of the whole project, how to make all components can dynamically switch their cache attributes is a problem worth considering.

The business scenario

1. The list page displays the details page, which contains headers and columns.

2. Access the row detail page from the row list and then return to the detail page. The detail page is not refreshed.

Such business scenario in the mobile terminal is very common, solution, of course, also have a lot of, such as every time a sign return details page is passed to the details page, and then do judgment in details page, according to mark to get inside the store is the latest data interface, do the judge not only trouble, and can’t do page-level caching, and memory resources. If you use keep-alive well, you can easily achieve this effect.

Overall design idea

1. Write three methods in store: setKeepAlive, setNoKeepAlive, and getKeepAlive. The functions are to set the components that need to be cached, clear the components that do not need to be cached, and obtain the components that need to be cached.

2. Get the name property of all components and store it in an array in the store.

3. Retrieve the cached component array when app.vue is mounted.

4. Dynamically set whether the page is cached or not in the page route blocking function.

5. Listen for store changes in app. vue and assign values to the array corresponding to include.

The specific implementation

1. Register three methods in store

const state = { keepAliveComponents:[], } const getters = { getKeepAlive (state) { return state.keepAliveComponents }, Open mutations = {setKeepAlive (state, component) {open mutations = {setKeepAlive (state, component) {open mutations = {setKeepAlive (state, component) {open mutations = {setKeepAlive (state, component) {open mutations = {setKeepAlive (state, component) { state.keepAliveComponents.includes(component) && state.keepAliveComponents.push(component) }, setNoKeepAlive (state, Component) {/ / delete don't cache component const index = state. KeepAliveComponents. IndexOf (component) index! == -1 && state.keepAliveComponents.splice(index, 1) }, } const actions = {} export default { state, getters, mutations, actions, }Copy the code

2. Set the cache for each component in the routing collection.

Routes. ForEach ((item) => {// In the routing global hook beforeEach, the function is to enter this component each time, Store.mit ('setKeepAlive', item.name)}) export default routesCopy the code

⚠️ Note: instead of store.mit (‘setKeepAlive’, item.component.name), item.name. It should have been store.mit (‘setKeepAlive’, item.component.name) because the include accepts the name of the component, but this name changes when packaged in the case of loading on demand, So start designing your project with the same route name and component name.

3. Get the cache component array when app. vue is mounted.

<template> <div id="app"> <transition :name="routerTransition"> <keep-alive :include="keepAliveComponentsData"> <router-view :key="$route.fullPath"></router-view> </keep-alive> </transition> </div> </template> <script> import Vue from 'vue' import store from './store' export default { name: 'App', data () { return { keepAliveComponentsData: [],}}, mounted () {/ / mount to acquiring the cache component enclosing keepAliveComponentsData = store. The getters. GetKeepAlive}} < / script >Copy the code

4. Dynamically change the cache properties of the page.

For example, if I want to jump from the detail page to the row detail, I cannot cache the row detail before jumping. If the row detail has cache, it will be the same every time I enter. So I’m going to clear the cache.

toLoanApplicationDetail (index) {
  store.commit('setNoKeepAlive', 'LoanlineReadonly')
}      
Copy the code

Now I need to jump from the row details to other associated documents, so I definitely need to cache the details, otherwise there is nothing back to the row details.

toContractDetail (item) {
	store.commit('setKeepAlive', 'LoanlineReadonly')
}      
Copy the code

5. Listen for cache changes.

Dynamic Settings are ok, I now need to dynamically bind the array to include, so I need to listen every time the page jumps.

<template> <div id="app"> <transition :name="routerTransition"> <keep-alive :include="keepAliveComponentsData"> <router-view :key="$route.fullPath"></router-view> </keep-alive> </transition> </div> </template> <script> import Vue from 'vue' import store from './store' export default { name: 'App', data () { return { keepAliveComponentsData: [], } }, mounted () { this.keepAliveComponentsData = store.getters.getKeepAlive }, watch: {/ / to monitor dynamic routing change Settings include data binding $route (to and from) {this. KeepAliveComponentsData = store. Getters. GetKeepAlive},}, } </script>Copy the code

Now that the usage is over, but the principle seems not very clear, let’s see how it is implemented.

The principle of analytic

The core idea of keep-alive is to cache components as VNodes, and then use the array included in include to match them. If a match is found, it is used directly. If the exclude changes, the corresponding VNodes are destroyed.

The source code parsing

Direct paste source code. Probably understand how to write comments inside

import { isRegExp, remove } from 'shared/util' import { getFirstComponentChild } from 'core/vdom/helpers/index' type VNodeCache = { [key: string]: ? VNode }; Function getComponentName (opts:? VNodeComponentOptions): ? String {return opts && (opts. Ctor. Options. The name | | opts. The tag)} / / a function the function test for a matching name matches (the pattern: string | RegExp | Array<string>, name: string): Boolean {// Array if (array.isarray (pattern)) {return pattern.indexof (name) > -1} else if (Typeof pattern === 'string') Return pattern.split(',').indexof (name) > -1} else if (isRegExp(pattern)) {// Regular return pattern.test(name)} /* // cache function pruneCache (keepAliveInstance: any, filter: Function) { const { cache, keys, _vnode} = keepAliveInstance for (const key in cache) { ?VNode = cache[key] if (cachedNode) { const name: PonentOptions? String = getComponentName (cachedNode.com) / * name is not in conformity with the filter condition, is not currently rendering vnode at the same time, destroy the vnode corresponding component instance (Vue instance), */ if (name &&! filter(name)) { pruneCacheEntry(cache, key, keys, _vnode) } } } } function pruneCacheEntry ( cache: VNodeCache, key: string, keys: Array<string>, current? : VNode ) { const cached = cache[key] if (cached && (! current || cached.tag ! = = current. The tag)) {/ * destroy vnode corresponding component instance (example Vue) * / cached.com ponentInstance. $destroy ()} cache [key] = null remove (keys, key) } const patternTypes: Array<Function> = [String, RegExp, Array] export default { name: 'keep-alive', abstract: true, props: { include: patternTypes, exclude: patternTypes, max: [String, Number]}, created () {/* Cache Object */ this.cache = object.create (null) this.keys = []}, /* Destroyed () {for (const key in this.cache) {pruneCacheEntry(this.cache, key, This.keys)}}, mounted () {/* $watch('include', val => {pruneCache(this, name => matches(val, name)) }) this.$watch('exclude', val => { pruneCache(this, name => ! Matches (val, name))})}, render () {/* Get the first component in the slot */ const slot = this.$slot.default const vnode: VNode = getFirstComponentChild(slot) const componentOptions: ?VNodeComponentOptions = vnode &&vnode.componentOptions if (componentOptions) {// Check pattern /* The component's name field is preferred, otherwise the component's tag */ 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 } 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 the component is already cached, fetch the component instance directly from the cache to vnode. */ if (cache[key]) {vnode.componentInstance = cache[key]. // 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, Enclosing _vnode)}} / * keepAlive flag bit * / vnode. Data. KeepAlive = true} return vnode | | (slot && slot [0])}}Copy the code

Simple summary

1. Created Hooks create a cache object that can be used as a cache container to hold vNodes. A Destroyed hook removes all component instances from the cache when the component is destroyed.

2. The render function mainly does these things:

• First get the first child component by getFirstComponentChild, and get the name of the component (use the component name if it exists, otherwise use tag).

• The name is then matched with the include and exclude attributes. If the match fails (indicating no caching is required), vNode is returned without any operation. Vnode is an object of type VNode.

• Include and exclude attributes support comma-separated string names such as “a,b,c” and regular expressions. Matches checks for matches to the current component in both ways, respectively.

• this. Cache is a cache based on the key. If there is a componentInstance, it has been cached before. Finally, return the VNode whose componentInstance has been replaced in the cache.

3. To monitor changes, use Watch to monitor changes in pruneCache and pruneCache, and modify the cached data in the cache during changes.

4.Vue. Js internally abstracts DOM nodes into vnodes one by one. The cache of keep-Alive components is also based on VNode nodes instead of directly storing DOM structures. The pruneCache and pruneCache components are cached in the cache object, and vNodes are removed from the cache and rendered when needed.

Ninecat – UI. Github. IO/ninecat- UI