At the end of the last section, which touched on Vue’s built-in components, we’ll begin with an analysis of a specific built-in component. The first is keep-alive, which is a component that we often use in daily development. When we switch between different components, we often need to keep them in state to avoid the performance cost of repeatedly rendering them. Keep-alive is often used in combination with the dynamic components described in the previous section. Due to the excessive content, the source analysis of keep-Alive will be divided into two parts, this section mainly focuses on the first rendering of keep-alive.
13.1 Basic Usage
The use of keep-alive simply requires adding a label to the outermost layer of the dynamic component.
<div id="app">
<button @click="changeTabs('child1')">child1</button>
<button @click="changeTabs('child2')">child2</button>
<keep-alive>
<component :is="chooseTabs">
</component>
</keep-alive>
</div>
var child1 = {
template: '<div><button @click="add">add</button><p>{{num}}</p></div>'.data() {
return {
num: 1
}
},
methods: {
add() {
this.num++
}
},
}
var child2 = {
template: '<div>child2</div>'
}
var vm = new Vue({
el: '#app',
components: {
child1,
child2,
},
data() {
return {
chooseTabs: 'child1', } }, methods: { changeTabs(tab) { this.chooseTabs = tab; }}})Copy the code
The simple result is as follows: The dynamic component switches back and forth between child1 and child2. When switched to child1 a second time, child1 retains its original data state, num = 5.
13.2 From Template Compilation to VNode Generation
Based on previous analysis experience, we will start with template parsing, and the first question is: is there any difference in the compilation process between a built-in component and a normal component? The answer is no. Whether it is a built-in component or a user-defined component, the component is essentially handled the same way when the template is compiled into the Render function. The details are not analyzed here. The result of the render function for keep-alive is as follows:
With the (this) {... _c (' keep alive - '{attrs: {" include ":" child2 "}}, [_c (chooseTabs, {tag: "component"})], 1)}
_c(‘keep-alive’··) will execute createElement to generate the component Vnode, where keep-alive is the component, The createComponent function is called to create the child Vnode. CreateComponent was previously discussed. The difference between this and creating a normal Vnode is that keep-alive VNodes will strip out unwanted property content. Since keep-alive attributes have no meaning inside the component except for the slot attribute, such as class style,
method has been deprecated since version 2.6. (Where abstract serves as the symbol of abstract component, and its function will be discussed later.)
// Create the subcomponent Vnode processfunctionThe createComponent (Ctordata, context, children, the tag) {/ / the abstract is the sign of built-in components (abstract)ifVar slot = data.slot; var slot = data.slot; var slot = data.slot; var slot = data.slot; data = {};if(slot) { data.slot = slot; }}}Copy the code
13.3 First render
Keep-alive is special because it does not render the same components repeatedly, but instead updates nodes using the cache retained by the first render. So to fully understand how it works, we need to start with the first render of keep-Alive.
13.3.1 flowchart
In order to clarify the process, I have drawn a flowchart which covers the initial rendering process of keep-Alive. The source code analysis will follow this process.
Vue will take the previously generated Vnode object and execute the real node creation process, which is the familiar patch process. The Patch execution phase will call createElm to create the real DOM. During the node creation process, The keep-alive VNode object is considered a component vnode, so the component Vnode executes the createComponent function, which initializes and instantiates the keep-alive component.
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
var i = vnode.data;
if(isDef(I)) {// isReactivated is used to check whether the component is cached. var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;if(isDef(I = i.hook) && isDef(I = i.hook)) {// Perform component initialization internal hook init I (vnode,false /* hydrating */);
}
if(isDef(vnode.ponentInstance)) {// initComponent(vnode, insertedVnodeQueue) keeps the real DOM in vnode; insert(parentElm, vnode.elm, refElm);if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true}}}Copy the code
The keep-Alive component initializes by calling its internal hook init method. Let’s see what init does first.
Var componentVNodeHooks = {init:function init (vnode, hydrating) {
if( vnode.componentInstance && ! vnode.componentInstance._isDestroyed && vnode.data.keepAlive ) { // kept-alive components, treat as a patch var mountedNode = vnode; // work around flow componentVNodeHooks.prepatch(mountedNode, mountedNode); }else{/ / will be assigned to the component instance vnode child = vnode.com ponentInstance = createComponentInstanceForVnode componentInstance attribute of the var (vnode, activeInstance ); child.$mount(hydrating ? vnode.elm : undefined, hydrating); }}, //function() {}}Copy the code
Vnode.data. keepAlive has no value for vNode.data. keepAlive. So will call createComponentInstanceForVnode method for component instantiation and the component instance assigned to vnode componentInstance attributes, eventually perform component instance $mount mount methods for instance.
CreateComponentInstanceForVnode is the process of component instantiation, the component instantiation began to say from the first of a series, is nothing but a series of options to merge, initialization, life cycle and so on initialization.
function createComponentInstanceForVnode (vnode, parent) {
var options = {
_isComponent: true, _parentVnode: vnode, parent: parent }; // Inline template processing, ignore this part of the code... // perform vue child instantiationreturn new vnode.componentOptions.Ctor(options)
}
Copy the code
13.3.2 Built-in Component Options
When we use components, we often define component options in the form of objects, such as Data, Method,computed, and so on, and register them in the parent or root component. Keep-alive also follows this truth, built-in two words also explains keep-alive is in the Vue source code built-in good option configuration, and has also registered to the global, this part of the source code can refer to in-depth analysis of Vue source code – Vue dynamic component concept, you will be messy? The built-in component constructor and the registration process are described at the end of the section. In this section we’ll focus on the keep-alive option.
Var keepalive = {name:'keep-alive'// Abstract component flags.true{include: patternTypes, exclude: patternTypes, Max: [String, Number]}, created:function created() {// Cache component vnode this.cache = object.create (null); This.keys = []; }, destroyed:function destroyed () {
for (var key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys);
}
},
mounted: function mounted () {
var thisThe $1= this; // Dynamic include and exclude // Listens to this of include Exclue.$watch('include'.function (val) {
pruneCache(thisThe $1.function (name) { return matches(val, name); });
});
this.$watch('exclude'.function (val) {
pruneCache(thisThe $1.function (name) { return! matches(val, name); }); }); }, // the keep-alive function render:function render() {// get the value of the slot under keep-alive var slot = this.$slots.default; Var vnode = getFirstComponentChild(slot); Var componentOptions = vnode && vnode.ponentOptions; // The first child of keep-alive existsifVar componentName = componentName (componentOptions); var componentName = componentOptions (componentOptions); var ref = this; var include = ref.include; var exclude = ref.exclude; // Check whether the subcomponent meets the cache matchif( // not included (include && (! name || ! matches(include, name))) || // excluded (exclude && name && matches(exclude, name)) ) {return vnode
}
var refThe $1 = this;
var cache = refThe $1.cache;
var keys = refThe $1.keys;
var key = vnode.key == null
? componentOptions.Ctor.cid + (componentOptions.tag ? ("... "" + (componentOptions.tag)) : ' ') : vnode.key; // Hit the cache againif (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 entryif(this.max && keys.length > parseInt(this.max)) { pruneCacheEntry(cache, keys[0], keys, this._vnode); } // Mark the cache component vNode.data. keepAlive =true; } // Return the rendered vNodereturn vnode || (slot && slot[0])
}
};
Copy the code
The keep-alive option is basically the same as the component option, except that the keep-ailve component uses the render function instead of template. Keep-alive is essentially just a process of storing and taking the cache, there is no actual node rendering, so render is the best choice.
13.3.3 cache vnode
Again, back to the analysis of the flow chart. As mentioned above, keep-alive performs component mount after component instantiation. Mount $mount back to vm._render(),vm._update(). Since keep-Alive has the render function, we can focus directly on the implementation of the render function.
-
- The first is to get
keep-alive
The contents of the lower slot, which iskeep-alive
Subcomponents that need to be rendered, in this casechil1 Vnode
Object in the source codegetFirstComponentChild
function
- The first is to get
function getFirstComponentChild (children) {
if (Array.isArray(children)) {
for(var i = 0; i < children.length; i++) { var c = children[i]; // If the component instance exists, the first component vnode is theoretically returnedif (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
return c
}
}
}
}
Copy the code
-
- Determine that the component meets the cache matching condition, in
keep-alive
During the use of components,Vue
The source code allows us to useinclude, exclude
To define the matching conditions,include
Specifies that only components whose names match will be cached,exclude
Specifies that any component with a matching name will not be cached. What’s more, we can usemax
To limit how many matching instances can be cached, and why do you want to limit the number? We’ll get to that later.
- Determine that the component meets the cache matching condition, in
After we get the instance of the child component, we need to determine whether the matching condition is met, where the matching rule allows the use of array, string, regular form.
var include = ref.include; var exclude = ref.exclude; // Check whether the subcomponent meets the cache matchif( // not included (include && (! name || ! matches(include, name))) || // excluded (exclude && name && matches(exclude, name)) ) {return vnode
}
// matches
functionMatches (pattern, name) {// Allow arrays ['child1'.'child2']
if (Array.isArray(pattern)) {
return pattern.indexOf(name) > -1
} else if (typeof pattern === 'string') {// The strings child1,child2 are allowedreturn pattern.split(', ').indexOf(name) > -1
} else if(isRegExp(pattern)) {// allow re /^child{1,2}$/greturn pattern.test(name)
}
/* istanbul ignore next */
return false
}
Copy the code
If the component does not meet the cache requirements, the vnode of the component is returned without any processing. In this case, the component starts the normal mounting process.
-
render
The key step in function execution is cachingvnode
, since it is the first executionrender
Function, in the optionscache
andkeys
None of the data has a value wherecache
Is an empty object that we’re going to use for caching{ name: vnode }
Enumeration,keys
We use it to cache component names.So we’re rendering for the first timekeep-alive
, the subcomponents that need to be renderedvnode
Cache.
cache[key] = vnode;
keys.push(key);
Copy the code
-
- Will have been cached
vnode
, and the child componentVnode
To return.vnode.data.keepAlive = true
- Will have been cached
13.3.4 Saving real nodes
Going back to the createComponent logic, we mentioned that the createComponent initializes the keep-Alive component first, including the mount of the child components. And we get the keep-alive componentInstance through componentInstance, and the next important step is to save the real dom into the vnode.
functionThe createComponent (vnode, insertedVnodeQueue) {...if(isDef(vnode.ponentInstance)) {// initComponent(vnode, insertedVnodeQueue) keeps the real DOM in vnode; Insert (parentElm, vnode.elm, refElm); // Insert (parentElm, vnode.elm, refElm);if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true}}Copy the code
The insert source code is not listed. It simply calls the DOM manipulation API to insert the child node into the parent node. We can focus on the logic of the key steps of initComponent.
function initComponent() {··· // vnode.elm = vnode.ponentInstance.$el; ...}Copy the code
So, we clearly go back to the question that was left. Whykeep-alive
The need for amax
To limit the number of cached components. The reason is thatkeep-alive
The cached component data except includesvnode
The object of this description remains truedom
Nodes, and we know that real node objects are large, so keeping a large number of cached components is performance intensive. Therefore, we need to strictly control the number of components that are cached, and we need to optimize the caching strategy, which we will continue to cover in the next article.
Because isReactivated to false, reactivateComponent function will not perform. At this point, the analysis of keep-Alive’s initial rendering process is complete.
If you ignore the analysis of the steps, just one summary of the initial render process: built-inkeep-alive
Component to allow the child component to be rendered for the first timevnode
And the realelm
Cached.
13.4 Abstract Components
This section ends with a passing mention of the concept of abstract components mentioned earlier. The built-in components provided by Vue have an option to describe the component type. This option is {astract: true}, which indicates that the component is abstract. What is an abstract component, and why this type of distinction? I think it comes down to two things.
-
- An abstract component has no real nodes, and it does not parse the renderings into real ones during the component render phase
dom
Nodes, but only as intermediate data transition layer processing, inkeep-alive
Is the handling of the component cache.
- An abstract component has no real nodes, and it does not parse the renderings into real ones during the component render phase
-
- When we introduced component initialization, we said that parent and child components explicitly establish a relationship that lays the foundation for communication between the parent and child components. We can review it again
initLifecycle
The code.
- When we introduced component initialization, we said that parent and child components explicitly establish a relationship that lays the foundation for communication between the parent and child components. We can review it again
Vue.prototype._init = function() {··· var vm = this; initLifecycle(vm) }function initLifecycle (vm) {
var options = vm.$options;
var parent = options.parent;
if(parent && ! Options. Abstract) {// If there is an abstract property, keep looking up until it is not abstractwhile (parent.$options.abstract && parent.$parent) {
parent = parent.$parent;
}
parent.$children.push(vm); }...}Copy the code
During the registration phase, the child component will mount the parent instance to the parent property of its option. During the initLifecycle process, it will get the parent vnode on the parent and add the child vnode for its $children property. If in the process of looking for the parent component in reverse, If a parent component has an abstract property, you can determine that the component is an abstract component. In this case, use the parent chain to look up until the component is not an abstract component. InitLifecycle processing enables each component to find the parent component at the upper level and the child component at the lower level, forming a tight relationship tree between components.
With the first cache processing, what magic does keep-alive have when the component is rendered a second time, and what cache optimizations are left? All of this will be solved in the next section.
- Vue source code – Options merge (top)
- Deep analysis of Vue source code – options merge (next)
- Deep analysis of Vue source – data agent, associated child parent components
- Deep analysis of Vue source code – instance mount, compilation process
- Deep analysis of Vue source code – complete rendering process
- Deep analysis of Vue source code – component foundation
- Deep analysis of Vue source code – components advanced
- Deep analysis of Vue source code – responsive system building (on)
- Deep analysis of Vue source code – responsive system construction (in)
- In-depth analysis of Vue source code – responsive system construction (next)
- Deep analysis of Vue source code – come, with me to achieve diff algorithm!
- Deep analysis of Vue source code – uncover Vue event mechanism
- Deep analysis of Vue source – Vue slot, you want to know all here!
- Vue source code – do you understand the V-Model syntax sugar?
- Deep analysis of Vue source code – Vue dynamic component concept, you will mess?
- Fully understand keep-alive magic in Vue (1)
- Fully understand the keep-alive magic in Vue (2)