Start the growth journey of Nuggets! This is the 15th day of my participation in the “Nuggets Daily New Project · February Update Challenge”, click to view the event details
keep-alive
Basic usage
The use of keep only needs to add labels to the outermost layer of dynamic components
<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 = tabs; } } })
When the dynamic component switches back and forth between child1 and child2, after the second switch to the child1 component, child1 retains the original data state.
Compile from template to virtual DOM
There is no difference between built-in components and ordinary components in the compilation process. No matter which component or user-defined component, the components are processed in the same way when the template is compiled into a render function. The generation process of the render function is not analyzed here.
After getting the render function, start to generate the virtual DOM. Since keep-alive is a component, it will call the createComponent function to create the virtual DOM of the sub-component. In the link of createComponent, the difference from creating a normal component is that the keep-alive Virtual DOM will remove redundant attributes, except for the slot attribute (slot attribute is also deprecated after version 2.6), other attributes are meaningless. On the virtual DOM of the keep-alive component, there is an abstract attribute as a sign of the abstract component.
// Create subcomponent Vnode process function createComponent(Ctordata,context,children,tag) {<!-- --> // abstract is the sign of built-in components (abstract components) if (isTrue(Ctor. options. abstract)) {<!-- --> // Only the slot attribute is retained, other label attributes are removed, and no longer exist on the vnode object var slot = data. slot; data = {<!-- -->}; if (slot) {<!-- --> data.slot = slot; } } }
Initial rendering
The reason why keep-alive
is special is that it will not render the same component repeatedly, but will only use the cache reserved for the first rendering to update nodes, in order to understand keep-alive
, we start to analyze his first rendering.
Process Analysis
The same as ordinary components, Vue
will get the virtual DOM
object generated earlier, and execute the process of creating real nodes, which is the process of patch
, in the process of creating a node, the virtual DOM
of keep-alive
will be considered as a vnode
of a component, so it will enter createComponent
function in which the keep-alive
component is initialized and instantiated.
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {<!-- --> let i = vnode.data if (isDef(i)) {<!-- --> // isReactivated is used to determine whether the component is cached const isReactivated = isDef(vnode.componentInstance) & amp; & amp; i.keepAlive if (isDef(i = i.hook) & amp; & amp; isDef(i = i.init)) {<!-- --> // Internal hook to perform component initialization init i(vnode, false /* hydrating */) } if (isDef(vnode. componentInstance)) {<!-- --> // One of the functions is to keep the real DOM in the virtual DOM initComponent(vnode, insertedVnodeQueue) insert(parentElm, vnode.elm, refElm) if (isTrue(isReactivated)) {<!-- --> reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm) } return true } } }
The keep-alive component will first call the internal init hook for initialization,
const componentVNodeHooks = {<!-- --> init (vnode: VNodeWithData, hydrating: boolean): ?boolean {<!-- --> if ( vnode.componentInstance & & !vnode.componentInstance._isDestroyed & & vnode.data.keepAlive ) {<!-- --> // kept-alive components, treat as a patch const mountedNode: any = vnode // work around flow componentVNodeHooks. prepatch(mountedNode, mountedNode) } else {<!-- --> // Copy the component instance to the componentInstance property of the virtual DOM const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ) // Mount after creating the component instance child.$mount(hydrating? vnode.elm : undefined, hydrating) } }, prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {<!-- --> //... }, insert (vnode: MountedComponentVNode) {<!-- --> //... }, destroy (vnode: MountedComponentVNode) {<!-- --> //... } }
In the first execution, there is no componentInstance
attribute in the component’s vnode
object, and vnode.data.keepAlive
has no value, so it will call createComponentInstanceForVnode
method instantiates the component and copies the component instance to the componentInstance
property of vnode, and finally executes the $mount method of the component instance to mount the instance.
createComponentInstanceForVnode
is the process of component instantiation. This process has been analyzed before, mainly including initialization operations such as option merger, initialization event, and life cycle.
Built-in component options
When using components, often define component options in the form of objects, including data
, method
, computed
, etc., and in the parent component or global Register to see the specific options of keep-alive
export default {<!-- --> name: 'keep-alive', abstract: true, // props values allowed by the keep-alive component props: {<!-- --> include: patternTypes, // Component names that need to be cached exclude: patternTypes, // Component names that do not need to be cached max: [String, Number] // The maximum number of caches, when this number is exceeded, it will be replaced according to the lru algorithm }, created () {<!-- --> // cache component vnode this.cache = Object.create(null) // Cache component name this.keys = [] }, destroyed () {<!-- --> for (const key in this.cache) {<!-- --> pruneCacheEntry(this.cache, key, this.keys) } }, mounted () {<!-- --> // For dynamic include and exclude, need to monitor this.$watch('include', val => {<!-- --> pruneCache(this, name => matches(val, name)) }) this.$watch('exclude', val => {<!-- --> pruneCache(this, name => !matches(val, name)) }) }, // render function render () {<!-- --> // Get the value of the slot under keep-alive const slots = this. $slots. default // Get the first vnode node const vnode: VNode = getFirstComponentChild(slot) // get the first component instance const componentOptions: ?VNodeComponentOptions = vnode & amp; & amp; vnode.componentOptions if (componentOptions) {<!-- --> // check pattern // Get the name attribute of the first subcomponent vnode const name: ?string = getComponentName(componentOptions) const {<!-- --> include, exclude } = this if ( // Determine whether the subcomponent needs to be cached, and return the vnode object directly if no cache is required // not included (include & amp; & amp; (!name || !matches(include, name))) || //excluded (exclude & amp; & amp; name & amp; & amp; 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 (cache[key]) {<!-- --> // Hit the cache, vnode.componentInstance = cache[key].componentInstance // make current key freshest // After deleting the key, add it again. The most recently used cache will be placed behind the array. This is the idea of the lru algorithm, which will be analyzed in detail later remove(keys, key) keys. push(key) } else {<!-- --> // first render, cache vnode cache[key] = vnode keys. push(key) // prune oldest entry if (this.max & amp; & amp; keys.length > parseInt(this.max)) {<!-- --> pruneCacheEntry(cache, keys[0], keys, this._vnode) } } // Add flags to the cache component vnode.data.keepAlive = true } return vnode || (slot & amp; & amp; slot[0]) } }
keep-alive
components are similar to normal components in terms of options, keep-alive
components use render
function instead of template
template. The keep-alive
component is essentially just a process of storing and fetching caches, without actual node rendering.
Cache VNode
After the keep-alive
component is instantiated, the component will be mounted, and the mounting process will return to the process of vm._render
and vm_update
. Since keep-alive
has render
function, we directly analyze the implementation of render
function
Get the content of the keep-alive component slot
The first is to obtain the content of the slot under keep-alive
through the getFirstComponentChild
method, which is the subcomponent that the keep-alive
component needs to render.
export function getFirstComponentChild (children: ?Array<VNode>): ?VNode {<!-- --> if (Array.isArray(children)) {<!-- --> for (let i = 0; i < children. length; i ++ ) {<!-- --> const c = children[i] // If the component instance exists, return it, theoretically return the vnode of the first component if (isDef(c) & amp; & amp; (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {<!-- --> return c } } } }
Cache component
After getting the subcomponent instance, you need to judge whether the matching conditions of the cache are met. The matching conditions can use arrays, strings, and regular expressions.
const name: ?string = getComponentName(componentOptions) const {<!-- --> include, exclude } = this if ( // Determine whether the subcomponent needs to be cached, and return the vnode object directly if no cache is required // not included (include & amp; & amp; (!name || !matches(include, name))) || //excluded (exclude & amp; & amp; name & amp; & amp; matches(exclude, name)) ) {<!-- --> return vnode } function matches (pattern: string | RegExp | Array<string>, name: string): boolean {<!-- --> // allows the use of arrays if (Array.isArray(pattern)) {<!-- --> return pattern. indexOf(name) > -1 } else if (typeof pattern === 'string') {<!-- --> // allows the use of strings return pattern.split(',').indexOf(name) > -1 } else if (isRegExp(pattern)) {<!-- --> // Allow regular forms return pattern.test(name) } /* istanbul ignore next */ return false }
If the component does not meet the cache requirements, it will return the vnode
of the component, and then enter the mounting process
The key step of the render
function is to cache the vnode
. Since the render
function is executed for the first time, the cache
and keys
has no data and cannot hit the cache. Therefore, when the keep-alive
component is rendered for the first time, the subcomponent vnode
that needs to be rendered will be rendered > for caching. Mark the cached vnode
and return the vnode
of the subcomponent, vnode.data.keepAlive = true
Saving of real nodes
Going back to the logic of crateComponent
, in the createComponent
method, the initialization process of the keep-alive
component will be executed first, including the subcomponents Mount, and then get the keep-alive
component instance in the createComponent
method, the next important step is to save the real DOM
in the createComponent
code>vnode.
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {<!-- --> let i = vnode.data if (isDef(i)) {<!-- --> // isReactivated is used to determine whether the component has a cache const isReactivated = isDef(vnode.componentInstance) & amp; & amp; i.keepAlive if (isDef(i = i.hook) & amp; & amp; isDef(i = i.init)) {<!-- --> // Internal hook to perform component initialization init i(vnode, false /* hydrating */) } if (isDef(vnode. componentInstance)) {<!-- --> // One of the functions is to keep the real DOM in the virtual DOM initComponent(vnode, insertedVnodeQueue) insert(parentElm, vnode.elm, refElm) if (isTrue(isReactivated)) {<!-- --> reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm) } return true } } } function initComponent (vnode, insertedVnodeQueue) {<!-- --> if (isDef(vnode.data.pendingInsert)) {<!-- --> insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert) vnode.data.pendingInsert = null } // Save the real DOM to vnode vnode.elm = vnode.componentInstance.$el if (isPatchable(vnode)) {<!-- --> invokeCreateHooks(vnode, insertedVnodeQueue) setScope(vnode) } else {<!-- --> // empty component root. // skip all element-related modules except for ref (#3455) registerRef(vnode) // make sure to invoke the insert hook insertedVnodeQueue.push(vnode) } }
When performing component caching, it is necessary to save the real DOM
node of the component to the vnode
object, and saving a large number of DOM
elements will consume a lot of performance , so we need to strictly control the number of cache components, and also need to optimize the cache strategy.
A summary of the first rendering of the keep-alive
component: the built-in keep-alive
component, when the subcomponent is rendered for the first time, the vnode
cached with the real DOM