The combination of examples has the following situation
<keep-alive>
<coma v-if="visible"></coma>
<comb v-else></comb>
</keep-alive>
<button @click="visible = ! visible">To change the</button>
Copy the code
For example, coma and Comb both have an input that has a corresponding value. If we don’t use keep-alive, when we change visible, both of these components will be rendered again, and the previous input will be lost, BeforeCreate => created… . But if we use keep-alive, then the value of the input on the visible switch is the same as the value of the last change. So keep-alive is primarily used to keep a component in state and to prevent it from being created over and over again.
The principle of
The usage of keep-alive is defined in core/ Components /keep-alive
export default {
abstract: true.props: {
include: patternTypes, // Cache whitelist
exclude: patternTypes, // Cache the blacklist
max: [String.Number] // The maximum number of instances in the cache
},
created() {
// To cache the virtual DOM
this.cache = Object.create(null);
this.keys = [];
},
mounted() {
// pruneCache is used to listen on the I whitelist if pruneCache is called
// pruneCache updates the vUE cache
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => ! matches(val, name)) }) } render() {/ /...}}Copy the code
The above code defines several operations to declare the cycle, the most important function render, let’s see how to implement
render
render () {
const slot = this.$slots.default
const vnode: VNode = getFirstComponentChild(slot) // Find the first child component object
constcomponentOptions: ? VNodeComponentOptions = vnode && vnode.componentOptionsif (componentOptions) { // Component parameters exist
// check pattern
constname: ? string = getComponentName(componentOptions)/ / component name
const { include, exclude } = this
if ( // Condition matching
// not included(include && (! name || ! matches(include, name))) ||// excluded
(exclude && name && matches(exclude, name))
) {
return vnode
}
const { cache, keys } = this
constkey: ? string = vnode.key ==null // Define the cache key for the component
// 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]) { // This component has been cached
vnode.componentInstance = cache[key].componentInstance
// make current key freshest
remove(keys, key)
keys.push(key) // Adjust the key sort
} else {
cache[key] = vnode // Cache component objects
keys.push(key)
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) { // If the number of caches exceeds the limit, delete the first one
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true // Rendering and executing the wrapped component's hook functions are required
}
return vnode || (slot && slot[0])}Copy the code
Perform a step-by-step analysis
- To obtain
keep-alive
Object contains the first child component object - Returns its own based on whether the white blacklist matches
vnode
- According to the
vnode
thecid
andtag
The generatedkey
, whether there is a current cache in the cache object, returns if so, and updateskey
inkeys
The position of - If the current cache object does not exist, go to
cache
Add the content of this, and according toLRU
The algorithm deletes instances that have not been used recently - Is set to the first child component object
keep-alive
fortrue
For the first time to render
When rendering the page for the first time, we can get the data of the child component, then store the vNode information of the child component in cache, and set keepAlive of the coma component to true. We know that vNode is generated by the render function. We know that vNode is generated by the render function. The render function is defined in platforms/web/entry- Runtime -with-compiler, and the template is compiled to render by compileToFunctions
<template>
<div class="parent">
<keep-alive>
<coma v-if="visible"></coma>
<comb v-else></comb>
</keep-alive>
</div>
</template>
<script>
(function anonymous(a) {
with(this) {
return _c('div', {
staticClass: "parent"
}, [
_c('keep-alive', [(visibility) ? _c('coma') : _c('comb')].1),
_c('button', {
on: {
"click": change
}
}, [_v("change")])], 1)}})</script>
Copy the code
You can see how keep-alive is generated in the generated Render function
_c('keep-alive', [(visibility) ? _c('coma') : _c('comb')].1),
Copy the code
In keep-alive, _c(‘coma’) is used to access the componentOptions of the child component. _c is defined in vDOM/creation-element.js, and is used to determine whether the component is generated by vnode or other components.
To change thedata
To triggerpatch
On the first render, let’s change the input value in coma to see if the input remembers the previous value when visible changes to true again. Because if you change the value of visible, this code will be re-executed
updateComponent = (a)= > {
vm._update(vm._render())
}
Copy the code
Therefore, the keep-alive function will be re-executed, since the data was stored in the cache during the first render, so this time the data will be fetched from the cache.
vnode.componentInstance = cache[key].componentInstance
Copy the code
When the key value does not exist in the first rendering, the vNode of the child component will be cached first. If the interruption point is used to see that the componentInstance is undefined in the first rendering, the vnode of the child component will be cached. ComponentInstance is actually generated by calling the init hook of the component in the patch process, so why can you get it at this time? Here is an example to explain such as the following example
a = {
b: 1
}
c = a;
a.b = 5;
console.log(c.b) / / 5
Copy the code
Object is a reference type, so when the original object changes, the reference will also change. So the previous state information is assigned to coma, and then why is the value assigned to coma, coma will not execute the component creation process? Look at the patch code, When executed to createComponent, since coma is a component, the component-related logic is executed
// core/vdom/patch.js
function createComponent(vnode, insertedVnodeQueue, parentElm, refELm) {
let i = vnode.data;
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false); }}}// core/vdom/create-component
init(vnode) {
if(vnode.componentInstance && ! vnode.componentInstance._isDetroyed && vnode.data.keepAlive) {const mountedNode: any = node;
componentVnodeHooks.prepatch(mountedNode, mountedNode)
} else {
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
child.$mount(vnode.elm)
}
}
Copy the code
Because vnode.ponentInstance has been reassigned to keepAlive and keepAlive is true, only prepatch is executed, so neither created nor mounted hooks are executed.
keep-alive
Self created andpatch
process
In core/ Instance /render, you can see the definition of updateComponent
updateComponent = (a)= > {
vm._update(vm._render())
}
Copy the code
So first, we call the render function of keep-alive to generate vnode, and then we call vm._update to perform patch operation. So what are the differences between keep-alive and normal components when they are first created and during patch process?
For the first time to render
Whether keep-Alive is an abstract component or not, it is still a component, so it will execute the logic of the component, and perform the patch operation in core/ VDOM /patch when it is first rendered
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)}}}Copy the code
Since this is the first time to render, the componentInstance does not exist, so we only execute the init hook, which creates the child componentInstance. But keep-alive components are abstract components. What makes an abstract component different from a normal component? As can be seen in core/ Instance/Lifecycle, components will only add themselves to the parent when they are not abstract and children will not add themselves to the abstract component $children. Again, this function is called from within vm._init
let parent = options.parent
if(parent && ! options.abstract) {while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
Copy the code
After changing the datapatch
process
When visible changes, will the keep-alive component be affected? The Patch article mentioned that updateComponent is triggered when the value in the data changes
updateComponent = (a)= > {
vm._update(vm._render())
}
Copy the code
The render function of keep-alive will be executed again, and the patch process of root component will be executed again. For detailed principle lessons, refer to the patch process of Vue source code. Here, the prepatch hook of keep-alive component is directly executed
To be solved
There is a problem that needs to be solved, the vnode needs to be regenerated every time it reaches the next tick, is there any way to optimize it, can it be replaced in another way, or does it have to be done? Can you think of any good ideas?
keep-alive
Is it necessary
As you can see, keep-alive is a great help in caching data and preventing components from being created repeatedly. Then there is the question of whether most components can use keep-alive to improve performance.
- What scenarios to use
In the page, we refresh the data if we return to the previous page, if we want to preserve the state when we left the page, then we need to usekeep-alive
- What scenarios are not used
Think about using firstkeep-alive
Is it necessary? If switching between two components does not require saving state, is it necessary? You might say yeskeep-alive
It saves performance, which is what we needactivated
Reset these properties. There are several risks to doing so- Are you sure you reset all the variables, and that the risk is manageable
- All the caches are in
cache
When there are too many components, there is too much content, resulting in a large object, and it is doubtful that the performance needs to be improved
Vue’s source analysis article will be updated all the time, please keep an eye on my Github