More articles

preface

I have not used VUE for some time, but this does not reduce my love for VUE. I would like to sort out the knowledge of VUE, check the omissions and fill the gaps, and attach the principles and codes. The length may be too long

Want to change a job, hangzhou has a small partner recommended

Vue and React

The same:

  1. The virtual DOM is used
  2. Provides responsive and componentized view components
  3. Keep the focus on the core library and leave other functions such as routing and global state management to the relevant libraries
  4. Provide lifecycle hook functions that developers can customize as needed

Differences:

  1. VueThe essence is the MVVM framework,ReactIs a front-end componentization framework
  2. VueCanonize templates, but support themJSXGrammar,ReactsupportJSXgrammar
  3. VueProvides instructions, filters, etc., for easy operation,ReactThere is no
  4. VueSupports bidirectional data binding
  5. VueEach component’s dependencies are tracked without the need to re-render the entire component tree.ReactAll child components are rerendered when the state of the application is changed. throughshouldComponentUpdateThis lifecycle approach can be controlled, butVueConsider this the default optimization
  6. The CSS scope is inReactIs implemented through csS-in-JS scheme,VueThrough the scoped
  7. VueA neutron component can pass messages to its parent in two ways: events and callback functions,VuePrefer to use events. inReactWe all use callback functions in the

What is MVVM? Different from MVC?

  • MVC

MVC is model-view-Controller. Model is the data Model, which is responsible for accessing data in the database. View is the View, which is what you see on the page. So it was left to the Controller to synchronize the Model and View. In the days of simple data, the Controller had no pressure to parse the data, but as the data structure became more and more complex, the Controller became very bloated. Then came the MVVM, Leave the parsing of the data to the VM

  • MVVM

MVVM is the abbreviation of Model-view-view Modle, which was first proposed by Microsoft

Model represents the data layer, View represents the View layer, is responsible for displaying data, ViewModle is responsible for synchronizing the relationship between Model and View, the core of the synchronous association is DOMListeners and DataBindings tools. DOMListeners are used to monitor DOM changes in the View and selectively upload them to the Model. The DataBindings tool is used to listen for Model data changes and update them to the View

The design idea of MVVM is to get rid of the complicated DOM. Instead of how to manipulate DOM, developers have changed to how to update the JS object state

For more details, see the difference between MVC and MVVM

The life cycle

  1. Create a stage
  • BeforeCreate(before component creation)

After instance initialization, $EL and data are undefined before data Observer and Watch/Event configuration are invoked

  • Created(after the component is Created)

Data observation and event configuration are complete. The data object is accessible, but not yet accessible, and ref is undefined. It is used for data initialization, Ajax, etc

  1. Mount the stage
  • BeforeMount(Before mounting components)

This function is called before the component is mounted, when the HTML template has been compiled, the virtual DOM exists, $EL is available, but ref is still unavailable

  • Mounted(After components are Mounted)

The component is mounted and the data is rendered. DOM is accessible, and this phase is performed only once

  1. Update the stage
  • BeforeUpdate(BeforeUpdate)

Called before data update and virtual DOM patching, the existing DOM can be accessed at this stage

  • Updated

Call after data update and virtual DOM patch

  1. Unloading phase
  • BeforeDestory(before uninstallation)

Called before instance destruction, which can be obtained through this and ref still exists, this phase is usually used to clear timers and bound listening events

  • Destroyed(after uninstallation)

The instance is destroyed, all instructions are unbound, and all event listeners are removed

  1. Keep-alive hook function
  • activated

Called when activated by a keep-alive cached component

  • deactivate

Called when a component cached by keep-alive is disabled.

Child and parent components communicate

  1. Parent component -> Child component
  • props
  • provide/inject
// provide inject dependencies into all descendant components
// Provide and inject binding are not responsive
// If you pass in a listening object, the object's property is still responsive
// parent
export default {
    data() {
        return {
            obj: {
                a: 1
            },
            sex: "Female"}},provide(){
        return {
            obj: this.obj, / / obj response
            sex: this.sex // sex is not responding}}}// child
export default {
    inject: ['obj'.'sex']}Copy the code
  • $attrs
// All parent components except class and style are accepted as attribute bindings that are not recognized as prop
// parent
<template>
    <Child :sex="' woman '" :name="' Lao wang '" :style="{}" />
</template>
// Child
export default {
    created() {
        console.log(this.$attrs) // {sex: "female"}}}Copy the code
  • $listeners
// Contains a v-ON event listener in the parent scope (without the.native modifier)
// parent
<template>
    <Child1 @click1="click1" @click2="click2" @click3="click3" />
</template>
// child1
<template>
    <Child2 v-on="$listeners" />// Pass down events bound to Child1</template>
export default {
    created() {
        console.log(this.$listeners) // { click1: f, click2: f, click3: f }
    },
    methods: {
        click1() {
            this.$emit('click1) } } } // child2 export default { created() { console.log(this.$listeners) // { click1: f, click2: f, click3: f } }, methods: { click2() { this.$emit("click2") }, click3() { this.$emit("click3") } } }Copy the code
  1. Child component -> Parent component
  • The callback function
  • $emit
  1. Brother components
  • State is promoted to the parent component
  1. general
  • vuex
  • Bus Event bus
// main.js
Vue.prototype.$bus = new Vue()
/ / a
this.$bus.$emit('test'.Awesome!)
/ / receive
this.$bus.$on('test'.(e) = > console.log(e)) / / 666
Copy the code

Principle of bidirectional binding

Vue adopts the data hijacking + subscriber publisher model. Data hijacking involves adding get and set methods to each attribute of component data via Object.defineProperty, which triggers listener callbacks in the set to notify subscribers when data changes

Step 1: Recursively traverse the data, adding getters and setters for the property via Object.defineProperty

Observer.prototype = {
    walk: function(data) {
        var self = this;
        Object.keys(data).forEach(function(key) {
            self.defineReactive(data, key, data[key]);
        });
    },
    defineReactive: function(data, key, val) {
        var dep = new Dep();
        var childObj = observe(val); // Object recursion
        Object.defineProperty(data, key, {
            enumerable: true.configurable: true.get: function() {},set: function(newVal) {}}); }}function observe(value, vm) {
    if(! value ||typeofvalue ! = ='object') return
    return new Observer(value);
};
Copy the code

Step 2: As an observer (collecting dependencies, publishing messages), the DEP registers the function when the getter is called and notifies the registered function when the setter is called

Object.defineProperty(data, key, {
    get: function() {
        if (Dep.target) {
            dep.addSub(Dep.target); // Register functions to collect dependencies
        }
        return val;
    },
    set: function(newVal) {
        if (newVal === val) return;
        val = newVal;
        dep.notify(); // Trigger update to publish messages}}); Dep.prototype = {addSub: function(sub) {
        this.subs.push(sub);
    },
    notify: function() {
        this.subs.forEach(function(sub) {
            sub.update(); // Watcher provides the update method}); }};Copy the code

Step 3: Compile parses the template instructions and replaces the variables with data. Then initialize render page view and update function and listen function on node binding corresponding to each instruction. The page is then updated whenever the data changes. When the page changes, the change information will be published accordingly

function Compile(el, vm) {
    this.vm = vm;
    this.el = document.querySelector(el);
    this.fragment = null;
    this.init();
}

Compile.prototype = {
    init: function () {
        if (this.el) {
            this.fragment = this.nodeToFragment(this.el);
            this.compileElement(this.fragment);
            this.el.appendChild(this.fragment);
        } else {
            console.log('Dom element does not exist '); }},nodeToFragment: function (el) {
        var fragment = document.createDocumentFragment();
        var child = el.firstChild;
        while (child) {
            // Move the Dom element into the fragment
            fragment.appendChild(child);
            child = el.firstChild
        }
        return fragment;
    },
    compileElement: function (el) {
        var childNodes = el.childNodes;
        var self = this;
        [].slice.call(childNodes).forEach(function(node) {
            var reg = / \ {\ {(. *) \} \} /;
            var text = node.textContent;

            if (self.isElementNode(node)) {  
                / / the node
                self.compile(node);
            } else if (self.isTextNode(node) && reg.test(text)) {
                // Parse the template if it is an instruction of the form {{}}
                self.compileText(node, reg.exec(text)[1]);
            }
            if (node.childNodes && node.childNodes.length) {
                // Recursive child nodeself.compileElement(node); }}); },compile: function(node) {
        var nodeAttrs = node.attributes;
        var self = this;
        Array.prototype.forEach.call(nodeAttrs, function(attr) {
            var attrName = attr.name;
            if (self.isDirective(attrName)) {
                var exp = attr.value;
                var dir = attrName.substring(2);
                if (self.isEventDirective(dir)) {  
                    // Event command
                    self.compileEvent(node, self.vm, exp, dir);
                } else {  
                    / / v - model instructionself.compileModel(node, self.vm, exp, dir); } node.removeAttribute(attrName); }}); },compileText: function(node, exp) {
        var self = this;
        var initText = this.vm[exp];
        // Initializes the initialized data into the view
        this.updateText(node, initText); 
        // Generate the subscriber and bind the update function, which is triggered by updaue in Watcher
        new Watcher(this.vm, exp, function (value) { 
            self.updateText(node, value);
        });
    },
    compileEvent: function (node, vm, exp, dir) {
        var eventType = dir.split(':') [1];
        var cb = vm.methods && vm.methods[exp];
        if (eventType && cb) {
            node.addEventListener(eventType, cb.bind(vm), false); }},compileModel: function (node, vm, exp, dir) {
        var self = this;
        var val = this.vm[exp];
        this.modelUpdater(node, val);
        new Watcher(this.vm, exp, function (value) {
            self.modelUpdater(node, value);
        });
        // Listen for input events, view changes update data
        node.addEventListener('input'.function(e) {
            var newValue = e.target.value;
            if (val === newValue) {
                return;
            }
            self.vm[exp] = newValue;
            val = newValue;
        });
    },
    updateText: function (node, value) {
        node.textContent = typeof value == 'undefined' ? ' ' : value;
    },
    modelUpdater: function(node, value, oldValue) {
        node.value = typeof value == 'undefined' ? ' ' : value;
    },
    isDirective: function(attr) {
        return attr.indexOf('v-') = =0;
    },
    isEventDirective: function(dir) {
        return dir.indexOf('on:') = = =0;
    },
    isElementNode: function (node) {
        return node.nodeType == 1;
    },
    isTextNode: function(node) {
        return node.nodeType == 3; }}Copy the code

Step 4: The Watcher subscriber acts as a bridge between the Observer and Compile. When instantiated, the Watcher subscriber adds itself to the DEP and provides an update method that triggers the corresponding function in Compile to complete the update when the data changes

function Watcher(vm, exp, cb) {
    this.cb = cb;
    this.vm = vm;
    this.exp = exp;
    this.value = this.get();  // Add yourself to the subscriber DEp
}

Watcher.prototype = {
    update: function() {
        this.run();
    },
    run: function() {
        var value = this.vm.data[this.exp];
        var oldVal = this.value;
        if(value ! == oldVal) {this.value = value;
            this.cb.call(this.vm, value, oldVal); // Triggers the corresponding update view function in Compile}},get: function() {
        Dep.target = this;  // Cache yourself
        var value = this.vm.data[this.exp]  // enforce get function in listener
        Dep.target = null;  // Release yourself
        returnvalue; }};Copy the code

Differences in computed and watch

  • Computed:
  1. Calculate attribute
  2. Support caching, only when the dependent data changes will be recalculated
  3. Asynchronous operations are not supported
  4. Automatically listens for changes in dependency values to dynamically return content

Usually a value is Computed depending on multiple other values. Computed by default has a getter, and setters need to be written manually, as follows:

computed: {
    num: {  
        get() {
            return this.num1 + this.num2
        },
        set(e) {
            console.log('do somethings')}}}Copy the code
  • Watch
  1. Listening to the
  2. Cache not supported
  3. Asynchronous operations can be performed
  4. Listening is a procedure that triggers a callback when the listening value changes

Watch sets immediate to true if it needs to start immediately after the component is loaded, and deep to true if it needs to listen for deep attributes

It is important to note that once deep is added, all attributes will be listened on, which is very performance expensive. Therefore, it is usually optimized to only listen on attributes that need to be listened on, such as obj. Not all operations that add deep will be listened for, such as object property obj.newAttr = 1 and array arr[0] = 1. Only those that are responsive can be listened for, such as push and pop

watch: {
    obj: {
        handler: function(newVal, oldVal) {
            console.log(newVal, oldVal)
        },
        immediate: true.deep: true}}Copy the code

Asynchronous queue, $nextTick

When data changes in the Vue, the DOM is not updated immediately and is usually tested using a for loop to avoid unnecessary calculations and DOM manipulation.

Each Watcher has a unique ID. When the same Watcher is triggered several times, it will determine whether it exists according to the ID to avoid repeated push into the queue. This is optimization made by Vue. So let’s take a look at the implementation of nextTick in Vue. Now that we know about nextTick, asynchronous queue updates are pretty much done

FlushCallbacks, execute all tasks
const callbacks = []
let pending = false
function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
// For compatibility, the use order is Promise->MutationObserver->setImmediate->setTimeout
When all synchronization tasks in flushCallbacks are completed, the asynchronous task is executed
// timerFunc
let timerFunc;
if (typeof Promise! = ="undefined" && isNative(Promise)) {
    const p = Promise.resolve();
    timerFunc = () = > {
        p.then(flushCallbacks);
        if (isIOS) setTimeout(noop);
    };
    isUsingMicroTask = true;
} else if (
!isIE &&
typeofMutationObserver ! = ="undefined" &&
(isNative(MutationObserver) ||
    // PhantomJS and iOS 7.x
    MutationObserver.toString() ===
    "[object MutationObserverConstructor]")) {let counter = 1;
    const observer = new MutationObserver(flushCallbacks);
    const textNode = document.createTextNode(String(counter));
    observer.observe(textNode, {
        characterData: true
    });
    timerFunc = () = > {
        counter = (counter + 1) % 2;
        textNode.data = String(counter);
    };
    isUsingMicroTask = true;
} else if (
typeofsetImmediate ! = ="undefined" &&
isNative(setImmediate)
) {
    timerFunc = () = > {
        setImmediate(flushCallbacks);
    };
} else {
    timerFunc = () = > {
        setTimeout(flushCallbacks, 0);
    };
}
// nextTick
export function nextTick (cb? :Function, ctx? :Object) {
    let _resolve
    The collection task is waiting to be executed
    callbacks.push(() = > {
        if (cb) {
            try {
                cb.call(ctx)
            } catch (e) {
                handleError(e, ctx, 'nextTick')}}else if (_resolve) {
            _resolve(ctx)
        }
    })
    if(! pending) {// Prevent subsequent nextTick from repeating timerFunc
        pending = true
        // Execute the task
        timerFunc()
    }
}
Copy the code

Ok, next up is the asynchronous task force

/ / update to update
update () {
    // Judgment of various circumstances...
    else {
        queueWatcher(this) // this is the current instance watcher}}// queueWatcher
const queue = []
let has = {}
let waiting = false
let flushing = false
let index = 0
export function queueWatcher (watcher: Watcher) {
    const id = watcher.id
    // Check whether the ID exists
    if (has[id] == null) {
        has[id] = true
        if(! flushing) { queue.push(watcher) }else {
            let i = queue.length - 1
            while (i > index && queue[i].id > watcher.id) {
                i--
            }
            queue.splice(i + 1.0, watcher)
        }
        if(! waiting) { waiting =true
            FlushSchedulerQueue is added to the callbacks in nextTick when the nextTick task is executed
            nextTick(flushSchedulerQueue)
        }
    }
}
// flushSchedulerQueue
function flushSchedulerQueue () {
    currentFlushTimestamp = getNow()
    flushing = true
    let watcher, id
    queue.sort((a, b) = > a.id - b.id)
    for (index = 0; index < queue.length; index++) {
        watcher = queue[index]
        if (watcher.before) {
            watcher.before()
        }
        id = watcher.id
        has[id] = null
        watcher.run() // Perform the update task}}Copy the code

As you can see, not only do we use nextTick on a daily basis, but Vue also uses its Api. To understand this you need to have some understanding of the JS implementation mechanism

The role of key in V-for

Why use key? There is no unique identifier to track whether a DOM is newly created or in a different position. The immortal is not likely to know about these changes. In other words, it gives the DOM an identity because the same tags in the DOM are: Div, P, etc. They look like clones, you can’t identify, why give an identity? In fact, it is still for optimization, optimization is the DIff algorithm, adding key and not adding key to calculate the patch is completely different, I take the example of the previous article to take a look (see Virtual Dom and diff algorithm for more information) :

const oldData = createElement('ul', { key: 'ul'},// Label ul: 0
    createElement('li', {},'aaa']), // li: 1 aaa: 2
    createElement('li', {},'bbb']), // li: 3 bbb: 4
    createElement('li', {},'ccc']) // li: 5 ccc: 6
])
const newData = createElement('ul', { key: 'ul' }, [
    createElement('li', {},'aaa']),
    createElement('li', {},'aaa']),
    createElement('li', {},'bbb']),
    createElement('li', {},'ccc']])const patches = diff(oldData, newData)
console.log('patches----------', patches)
Copy the code

According to the patches results, we can see the changes of corresponding identification nodes. Let’s look at another set of data

const oldData = createElement('ul', { key: 'ul' }, [
    createElement('li', { key: 1},'aaa']),
    createElement('li', { key: 2},'bbb']),
    createElement('li', { key: 3},'ccc']])const newData = createElement('ul', { key: 'ul' }, [
    createElement('li', { key: 1},'aaa']),
    createElement('li', { key: 4},'aaa']),
    createElement('li', { key: 2},'bbb']),
    createElement('li', { key: 3},'ccc']])const patches = diff(oldData, newData)
console.log('patches----------', patches)
Copy the code

It can be seen that the patch without key is indeed a lot of complex, daily complex operations and do not know how many times more complex

Another problem is that the index index is avoided in the for loop, because a change in one element can cause all subsequent DOM representations to change, making the patch more complex and the update less efficient

V-if and V-show differences

Optimization is everywhere, so distinguish between V-if and V-show for optimization

  • v-if

True conditional rendering, destruction or reconstruction of the DOM, cost performance, and event listeners and subcomponents within conditional blocks are properly destroyed and reconstructed during switching

In addition, V-for has a higher priority than V-IF. Using v-FOR also causes performance problems, which are usually solved by computed or V-show

  • v-show

Simple CSS switching (display), low cost, frequent switching is commonly used

Data in Vue components must be functions

In fact, this is a basic problem of JS, because the same object is reused the same address will affect each other, that is, a component is referenced for many times this data will be messed up, so use the function to return a new object

Vue initialization page flash problem

<! -- css -->
[v-cloak] { display: none; }
<! -- html -->
<div v-cloak>
  {{ message }}
</div>
Copy the code

Vue and React routing modes

Front-end routes are hash routes and history routes, as well as vue-router and react-router

  • Hash routing

Hash routes have hash urls that change page content by listening for hashchange events

<a href="#/page1">page1</a>
<a href="#/page2">page2</a>
<div id="app"></div>
Copy the code
const app = document.getElementById('app');
window.addEventListener('onload',hashChange() )
window.addEventListener('hashchange'.() = > hashChange())

function hashChange() {
    switch (window.location.hash) {
        case '#/page1':
            app.innerHTML = 'page1'
            return
        case '#/page2':
            app.innerHTML = 'page2'
            return
        default:
            app.innerHTML = 'page1'
            return}}Copy the code
  • history

History adds pushState and replaceState (pushState pushes the incoming URL onto the history stack, and replaceState replaces the incoming URL with the current history stack). They provide the ability to modify the history. It’s just that when they make changes that change the current URL, the browser doesn’t immediately send requests to the back end

<a href="/page1">page1</a>
<a href="/page2">page2</a>
<div id="app"></div>
Copy the code
window.addEventListener('DOMContentLoaded', Load)
window.addEventListener('popstate', PopChange)
var routeView = null
function Load() {
    routeView = document.getElementById('app')
    // The popState callback is executed once by default
    PopChange()
    var aList = document.querySelectorAll('a[href]')
    aList.forEach(aNode= > aNode.addEventListener('click'.function (e) {
        e.preventDefault() 
        var href = aNode.getAttribute('href')
        history.pushState(null.' ', href)
        // PopState does not listen for changes in the address bar, so you need to manually execute the PopChange callback function
        PopChange()
    }))
}
function PopChange() {
    switch (window.location.pathname) {
        case '/page1':
            routeView.innerHTML = 'page1'
            return
        case '/page2':
            routeView.innerHTML = 'page2'
            return
        default:
            routeView.innerHTML = 'page1'
            return}}Copy the code

The history mode needs to be configured in the background. If the URL cannot match static resources, the same index.html is returned. Take nginx as an example:

location / {
  try_files $uri $uri/ /index.html;
}
Copy the code

The background configuration

Vue-router Specifies the route hook function

The hook function of the route is usually called the guard of the route. The guard is the gatekeeper, who wants to let you in or not. If you meet the criteria, you will be let in. For authentication, there are many types of “gates”, such as the main gate, the small door, etc., so routing hooks are also differentiated:

  • Global hooks

This is the gatekeeper of the main gate. If you can’t get through here, you won’t be able to go anywhere

// Common hook: check before jump
router.beforeEach((to, from, next) = > {
  // ...
})
Copy the code
  • Exclusive to hook

There were always special people in the palace, and he was special, so he had his own porters.

const router = new VueRouter({
  routes: [{path: '/foo'.component: Foo,
      beforeEnter: (to, from, next) = >{}}]})Copy the code
  • Component hook

Each room is not so special, but there will be a door, want to come in also depends on whether there is a “doorkeeper”, “doorkeeper” mood

export default {
    data() {},
    beforeRouteEnter(to, from, next) {}, // You don't know who the owner is, so this is undefined at this stage
    beforeRouteLeave (to, from, next) {}
}
Copy the code

How does Vue dynamically route

  • Dynamic routes provided by router

Both Vue and React provide dynamic matching in /: ID mode, which is usually used to match pages of the same structure

  • Dynamic permission routing

Dynamic routing is usually required for permission control, and there are two approaches:

  1. The front end has a full amount of routing data, and then processes the full amount of routing data according to the permission data of the request background. The unqualified data will be filtered out and not displayed in the menu bar or other places on the page. When the page jumps, the route guard will judge whether there is permission or not

  2. Front-end routes are divided into static and dynamic routes. Static routes, such as login page and 404, can be directly written into the routes, while dynamic routes are provided by the back-end and added to the front-end routes through router.addRoutes. There are two ways to deal with this problem:

// Use window.location.href
window.location.href = '/login'
// 2. Clear the routing information before adding routes
// router.js
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

const createRouter = () = > new Router({
  mode: 'history'.routes: []})const router = createRouter()
export function resetRouter () {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher 
}
export default router
// Call resetRouter before calling addRoutes
import {resetRouter} from '@/router'
resetRouter()
router.addRoutes(routes)
Copy the code

keep-alive

A common example of caching inactive component instances rather than destroying them is when tabs are switched back and forth and the TAB state is not initialized

// include: the value can be a string or a regular
// Cache components named A and B, other components are not cached
<keep-alive include="a,b">
  <component :is="view"></component>
</keep-alive>
// Exclude has the same value as include
// Max Maximum number of cache components

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

Keep-alive itself is also a component. It is a component of Vue’s internal implementation, but it will not be rendered. Keep-alive has an attribute {abstract: True}, abstract will be ignored when it is true. During the first rendering, the keep-alive component will directly return vNode if it does not meet the condition. The component that meets the condition will cache vNode through the object cache. A cached vnode componentInstance is overlaid on top of the current vnode

// keep-alive.js
export default {
    name: 'keep-alive'.abstract: true.props: {
        include: patternTypes,
        exclude: patternTypes,
        max: [String.Number]
    },
    created () {
        this.cache = Object.create(null) // Store the cache
        this.keys = []
    },
    destroyed () {
        for (const key in this.cache) {
            pruneCacheEntry(this.cache, key, this.keys) // Clear the cache through pruneCacheEntry}},// The cache is updated using pruneCache
    mounted () {
        this.$watch('include'.val= > {
            pruneCache(this.name= > matches(val, name))
        })
        this.$watch('exclude'.val= > {
            pruneCache(this.name= >! matches(val, name)) }) }, render () {const slot = this.$slots.default // Get the default slot
        const vnode: VNode = getFirstComponentChild(slot) // Get the first element
        constcomponentOptions: ? VNodeComponentOptions = vnode && vnode.componentOptionsif (componentOptions) {
        // check pattern
        constname: ? string = getComponentName(componentOptions)const { include, exclude } = this
        if (
            // not included(include && (! name || ! matches(include, name))) ||// excluded
            (exclude && name && matches(exclude, name))
        ) {
            return vnode // Return vnode if the cache is not hit
        }

        const { cache, keys } = this
        constkey: ? 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]) { // The cache already exists
            vnode.componentInstance = cache[key].componentInstance
            // make current key freshest
            remove(keys, key)
            keys.push(key)
        } else { // Not cached
            cache[key] = vnode
            keys.push(key)
            // prune oldest entry
            if (this.max && keys.length > parseInt(this.max)) {
                pruneCacheEntry(cache, keys[0], keys, this._vnode)
            }
        }
            vnode.data.keepAlive = true
        }
        return vnode || (slot && slot[0])}}// pruneCache
function pruneCache (cache: VNodeCache, current: VNode, filter: Function) {
    for (const key in cache) {
        constcachedNode: ? VNode = cache[key]if (cachedNode) {
            constname: ? string = getComponentName(cachedNode.componentOptions)// Clear the cache if there is no match
            if(name && ! filter(name)) {if(cachedNode ! == current) { pruneCacheEntry(cachedNode) } cache[key] =null}}}}// pruneCacheEntry
function pruneCacheEntry (vnode: ? VNode) {
    if (vnode) {
        vnode.componentInstance.$destroy()
    }
}
Copy the code

The first render will be like a normal render, but subsequent renders will insert the cached DOM into the parent element

// The patch process executes createComponent
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 */)}// Render vnode.componentInstance === cache[key]. ComponentInstance === cache[key]
        if (isDef(vnode.componentInstance)) {
            initComponent(vnode, insertedVnodeQueue)
            insert(parentElm, vnode.elm, refElm) // This inserts the cached DOM (vnode.elm) into the parent element
            if (isTrue(isReactivated)) {
                reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
            }
            return true}}}Copy the code

conclusion

Continuously updated… (Feels like some modules can be pulled out for further study)