introduce

Vue. Js is what

Vue (pronounced vju/curliest, similar to View) is a set of progressive frameworks for building user interfaces.

Vue’s core library focuses only on the view layer, making it easy to get started and integrate with third-party libraries or existing projects. On the other hand, when combined with modern toolchains and various supporting libraries, Vue is perfectly capable of providing drivers for complex, single-page applications.

The initial Vue. Js

Create a Demo. HTML <! -- div id= <div id="app"> <! - {{}} interpolation formula, the data binding vue data - > {{message}} < / div > <! -- Import vue. Js library --> <script SRC ="vue.min.js"></script>
<script>
    Create a vue object
    new Vue({
        el: '#app'.// Bind the scope of vue
        data: {// Define the model data to display on the page
            message: 'Hello Vue! '
        }
    })
</script>
Copy the code

This is declarative rendering: at the heart of Vue. Js is a system that allows for declarative rendering of data into the DOM using concise template syntax

The core idea here is that there are no tedious DOM operations. For example, in jQuery, we need to find the DIV node, get the DOM object, and then perform a series of node operations

Creating snippets

Create code snippets in VS Code:

File => Preferences => User snippets => New global snippets/or folder snippets: vue-html.code-snippets

Note: When creating code snippets, replace the string with a space bar if it contains the Spaces of the “Tab” key copied from the file

{
    "vue htm": {
        "scope": "html"."prefix": "vuehtml"."body": [
            "
      "."<html lang=\"en\">".""."<head>"." 
      "."
      ." 
      "." Document"."</head>".""."<body>"." 
      
"
.""." "." "." ." new Vue({"." el: '#app',"." data: {"."$1"."}"."})"." "."</body>".""."</html>",]."description": "my vue template in html"}}Copy the code

Shared goals:

  • Understand the componentization mechanism of vue.js
  • Understand the responsive system principle of vue. js
  • Understand Virtual DOM and Diff principle in vue. js

Summary of Vue. Js

Vue is a set of progressive MVVM frameworks for building user interfaces. How do you think about incremental? Progressive meaning: minimum mandatory claims.Vue.js includes declarative rendering, component-based systems, client routing, large-scale state management, build tools, data persistence, cross-platform support, etc. However, in the actual development, developers are not forced to follow a specific function, but gradually expand according to the needs. The core library of Vue.js is only concerned with view rendering, and due to its progressive nature, vue.js is easy to integrate with third-party libraries or existing projects.

Mechanism of components

Definition:

A component is an independent encapsulation of a function and style, allowing HTML elements to be extended, so that code can be reused, making development flexible and more efficient.

Like HTML elements, components in vue.js have external attributes prop and events. In addition, components also have their own state data and computed properties calculated through data and state. The dimensions combined determine how the components look and interact with each other.

The data transfer

Each component is scoped in isolation, which means that there should be no references to data between components, and even if there are references, components are not allowed to manipulate data outside of the component itself. Vue allows you to pass prop data to components that explicitly declare the prop field. Declare a Child component as follows:

<! -- child.vue --> <template>
    <div>{{msg}}</div>
</template>
<script>
export default {
    props: {
        msg: {
            type: String,
            default: 'hello world' // If default is a reference type, return function
        }
    }
}
</script>
Copy the code

The parent component passes data to this component:

<! -- parent.vue --> <template>
    <child :msg="parentMsg"></child>
</template>
<script>
import child from './child';
export default {
    components: {
        child
    },
    data () {
        return {
            parentMsg: 'some words'
        }
    }
}
</script>
Copy the code

events

Vue internally implements an EventBus system, called EventBus. The idea of using EventBus as a bridge in Vue is that each component instance of Vue inherits EventBus and can accept events on and emit events.

If the child.vue component wants to modify the parentMsg data of the parent. Vue component, what can be done? In order to ensure the traceability of data flow, it is not recommended to modify the MSG field of prop in the component directly, and in the example, it is a non-reference String, so it cannot be modified directly. In this case, the event of modifying parentMsg should be passed to child.vue. Let child.vue trigger the event to modify parentMsg. Such as:

<! -- child.vue --> <template>
    <div>{{msg}}</div>
</template>
<script>
export default {
    props: {
        msg: {
            type: String,
            default: 'hello world'
        }
    },
    methods: {
        changeMsg(newMsg) {
            this.$emit('updateMsg', newMsg);
        }
    }
}
</script>
Copy the code

The parent component:

<! -- parent.vue --> <template>
    <child :msg="parentMsg" @updateMsg="changeParentMsg"></child>
</template>
<script>
import child from './child';
export default {
    components: {
        child
    },
    data () {
        return {
            parentMsg: 'some words'
        }
    },
    methods: {
        changeParentMsg: function (newMsg) {
            this.parentMsg = newMsg
        }
    }
}
</script>
Copy the code

Parent component parent. Vue passes the updateMsg event to child component Child. vue. When the child component instantiates, the child component registers the updateMsg event with the on function.

In addition to event passing between parent and child components, a Vue instance can be used to bridge data communication between parent and child components at multiple levels, such as:

const eventBus = new Vue();

// The parent component uses $on to listen for events
eventBus.$on('eventName', val => {
    //  ...do something
})

// The child component fires the event using $emit
eventBus.$emit('eventName'.'this is a message.');
Copy the code

In addition to ON and EMIT, the event bus system provides two other methods, once and off, with the following events:

  • $on: Monitors and registers events.
  • $emit: Triggers the event.
  • $once: registers the event. The event is allowed to be triggered only once. The event is removed immediately after the event is triggered.
  • $off: Removes the event.

Content distribution

Vue implements a content distribution system that follows the Draft Web Components specification, using elements as an outlet to host the distribution.

Slots slots, also a component’s HTML template, are displayed or not, and how they are displayed is up to the parent component. In fact, the two core issues of a slot are highlighted here: whether to display and how to display.

Slot and default slot, named slot.

The default slot

A component can have only one slot of this type, as opposed to a named slot.

Such as:

<template> <! -- Parent component parent-vue --> <divclass="parent"> <h1> Parent </h1> <child> <divclass="tmpl"> < span > menu1</span>
        </div>
    </child>
</div>
</template>
Copy the code
<template> <! -- Child component child.vue --> <divclass="child">< H1 > subcomponent </h1> <slot></slot> </div> </template>
Copy the code

As shown above, the slot tag of the child component is replaced by the div.tmpl passed in by the parent component at render time.

A named slot

An anonymous slot is called an anonymous slot because it does not have a name attribute. Then, the slot becomes a named slot by adding the name attribute. A named slot can appear N times in a component, in different locations, with different name attributes.

Such as:

<template> <! -- Parent component parent-vue --> <divclass="parent"> <h1> Parent </h1> <child> <divclass="tmpl" slot="up"> <span> menu up- 1</span>
        </div>
        <div class="tmpl" slot="down"> <span> Menu down- 1</span>
        </div>
        <div class="tmpl"> <span> menu ->1</span>
        </div>
    </child>
</div>
</template>
Copy the code
<template>
    <div class="child"> <! -- named slot --> <slot name="up"></slot> <h3> Here are the sub-components </h3> <! -- named slot --> <slot name="down"></slot> <! -- Anonymous slot --> <slot></slot> </div> </template>
Copy the code

As shown above, the slot tag replaces the value of the slot attribute passed in by the parent to the child tag.

If the slot name value is default, the slot name value is default. If the slot name value is default, the slot name value is default.

Scope slot

A scoped slot can be either a default slot or a named slot. The difference is that a scoped slot can bind data to a slot label so that its parent component can get data from a child component.

Such as:

<template> <! -- parent.vue --> <divclass="parent"> <h1> This is the parent component </h1> <current-user> <template slot="default" slot-scope="slotProps">
                {{ slotProps.user.name }}
            </template>
        </current-user>
    </div>
</template>
Copy the code
<template> <! -- child.vue --> <divclass="child"> <h1> This is the child component </h1> <slot :user="user"></slot>
    </div>
</template>
<script>
export default {
    data() {
        return {
            user: {
                name: 'xiehuangbao1123'
            }
        }
    }
}
</script>
Copy the code

In the example above, when rendering the default slot, the child component passes the user data to the slot tag. During rendering, the parent component can obtain the user data through the slot-scope property and render the view.

Slot implementation principle:

The default slot is vm.slot.default, the named slot is vm.slot. XXX, and XXX is the slot name. In this case, data can be passed to the slot. If data exists, the slot can be used as a scoped slot.

So far, the parent-child component relationship is shown as follows: Template rendering

At the heart of Vue.js is declarative rendering. Unlike imperative rendering, declarative rendering simply tells the program what we want it to look like and lets the program do the rest. Imperative rendering, on the other hand, requires the command program to perform the rendering step by step according to the command. The following example distinguishes:

var arr = [1.2.3.4.5];

// Imperative rendering, care about each step, care about the flow. Do it by command
var newArr = [];
for (var i = 0; i < arr.length; i++) {
    newArr.push(arr[i] * 2);
}

// Declarative rendering, do not care about the intermediate process, only need to care about the result and implementation conditions
var newArr1 = arr.map(function (item) {
    return item * 2;
});
Copy the code

Vue.js implements directives such as if, for, events, and data binding, allowing for concise template syntax to declaratively render data out of view.

Template compilation

Why template compilation? In fact, the template syntax in our component is not parsed by the browser because it is not the correct HTML syntax, whereas template compilation is the compilation of the component’s template into executable JavaScript code, which translates the template into an actual rendering function.

Template compilation is divided into three stages, parse, optimize, generate, and finally generate the render function.

The parse phase:

Template is parsed as a string using an expression in progress to obtain instructions, class, style and other data, and generate an abstract syntax tree AST.

Optimize phase:

Look for static nodes in the AST to mark and optimize the comparison for VNode in the patch process. Nodes marked as static are ignored in subsequent diff algorithms without detailed comparisons.

The generate phase:

Concatenate a string of render functions according to the AST structure.

precompiled

For Vue components, template compilation is compiled only once, when the component is instantiated, and not after the rendering function is generated. Therefore, compiling is a performance drain on the component runtime. The purpose of template compilation is simply to convert the template into a render function, and this process can be completed during project construction.

For example, vue-loader of Webpack relies on vue-template-compiler module. During webpack construction, template is precompiled into render function, and the template compilation process can be directly skipped at runtime.

Looking back, the Runtime needs to be just the render function, and once we have pre-compiled, we just need to make sure that the render function is generated during the build. Like React, after adding JSX’s syntactic sugar compiler babel-plugin-transform-vue-jsx, we can write render functions directly in vue components using JSX syntax.

<script>
export default {
    data() {
        return {
            msg: 'Hello JSX.'
        }
    },
    render() {
        const msg = this.msg;
        return <div>
            {msg}
        </div>;
    }
}
</script>
Copy the code

With JSX, we can use HTML tags directly in our JAVASCRIPT code, and we no longer need to declare the template after we declare the render function. Of course, if we declare both the template tag and the render function, the template compilation will override the original render function during the build process, with the template taking precedence over the directly written render function.

Compared to Template, JSX is more flexible and has a natural advantage in dealing with complex components. Template, though a bit dull, has a simpler, more intuitive, and more maintainable code structure that is more consistent with the separation of view and logic.

Note that the resulting render function is run wrapped in the with syntax.

summary

Vue component transmits data through PROP and implements EventBus, which is integrated to monitor event registration, trigger event, and distribute content through slot.

In addition, the implementation of a declarative template system, at runtime or precompilation is to compile templates, generate rendering functions for component rendering view.

Responsive system

Vue. Js is a MVVM JS framework, when the data model data is modified, the view will be automatically updated, that is, the framework helps us complete the DOM update operation, without us manually operating DOM. When we assign values to the data, Vue tells all the components that depend on the data model that the data you depend on has been updated and you need to re-render. At this point, the component will re-render, completing the update of the view.

Data model, calculation properties, and listeners

Within the component, you can define a data model, data, computed property, and a listener, watch, for each component.

Data model: During the creation of the Vue instance, each attribute of the data model data is added to the responsive system. When the data is changed, the view gets the response and updates synchronously. Data must be returned as a function. Data wrapped without return will be visible globally in the project, resulting in variable pollution. Variables in data wrapped with return only take effect in the current component and do not affect other components.

Computed properties: Computed results are cached based on component responsiveness dependencies. They are recalculated only if the relevant responsive dependencies change, that is, only if the responsive data it depends on (data, PROP, computed itself) changes. So when should you use computed properties? Expressions in templates are very convenient, but they are designed for simple calculations. Putting too much logic into a template can make it too heavy and difficult to maintain. For any complex logic, you should use computed properties.

Listener: As its name suggests, the listener Watch can monitor changes in responsive data, including Data, PROP, computed, and handle changes in responsive data accordingly. This approach is most useful when asynchronous or expensive operations need to be performed when data changes.

Response principle

In Vue, all attributes under the data model are proxyed by Vue using Object.defineProperty (Vue3.0 uses Proxy). The core mechanism of responsiveness is the observer mode, in which data is the observed party and changes are notified to all observers so that the observers can respond, for example, when the observer is a view, the view can make updates to the view.

Vue. Js has three important concepts: Observer, Dep and Watcher.

A publisher – the Observer

  1. Observe plays the role of publisher. His primary role is to call defineReactive when the component VM is initialized, using object.defineProperty to hijack/listen on each of the child attributes of the Object. That is, add getters and setters for each property to make the corresponding property value reactive.
  2. During component initialization, the initState function is called and initState, initProps, and initComputed methods are executed internally to initialize data, Prop, and computed, respectively, so that they become responsive.
  3. When initializing props, traverse all props, call defineReactive, make each prop value reactive, and then mount it to _props. XXX is then propped to vm._props.
  4. Similarly, when initializing data, as with prop, we traverse all data, call defineReactive, make each data attribute value reactive, then mount it to _data, and then proxy vm. XXX to vm._data.xxx via proxy.
  5. To initialize computed, first create an observer Object, computed-watcher, then iterate over each attribute of computed, call the defineComputed method on each attribute value, and use Object.defineProperty to make it responsive, Proxy it to a component instance and you can access XXX to compute properties through vm. XXX.

Dispatch center/subscriber -Dep

  1. Dep plays the role of scheduling center/subscriber. In the process of calling defineReactive to make the attribute value responsive, it instantiates a Dep for each attribute value. Its main function is to manage the Watcher, collect the observer and notify the observer of target updates. The observer list (dep.subs) is iterated over, notifying all watchers and letting subscribers perform their own update logic.
  2. Its DEP task is in the attribute ofgetterMethod, calldep.depend()Method, the observer (i.eWatcherMay be componentrender

Function, possibly computed or attribute listener watch, is kept internally to complete its dependency collection. In the setter method of the property, the dep.notify() method is called to notify all observers to perform the update, completing the dispatch of the update.

Observer – Watcher

Watcher plays the role of subscriber/observer. His main role is to provide a callback function for the observed properties and collect dependencies. When the observed value changes, Watcher receives notification from the dispatch center Dep and triggers the callback function.

Watcher can be divided into three categories: normal-watcher, computed- Watcher and render- Watcher.

  • Normal-watcher: defined in the component hook function watch, that is, any change in the listening property triggers the defined callback function.
  • Computed – Watcher: Defined in the component hook function computed, each computed property generates a corresponding Watcher object, but this type of Watcher has one characteristic: When the calculated property depends on other data, the property is not recalculated immediately, but only computed later when the property needs to be read elsewhere, which is called lazy computing.
  • Render – Watcher: Every component has a render-watcher that is called to update the component’s view when properties in data/computed change.

The three types of Watcher also have a fixed execution order, namely, computed-render -> normal-watcher -> render- Watcher. This ensures that, as much as possible, the computed property is the latest value when the component view is updated. If render- Watcher is ranked before compute-render, the computed value will be old when the page is updated.

summary

The Observer is responsible for intercepting data, Watcher is responsible for subscribing and observing data changes, Dep is responsible for receiving subscriptions and notifying observers, and Dep is responsible for receiving publications and notifying all Watcher.

Virtual DOM

In Vue, the template is compiled into a browser-executable render function, which is then mounted in a render- Watcher with a responsive system. When data changes, The dispatch center Dep tells the render- Watcher to perform the Render function to complete the view rendering and update.

The whole process seems smooth, but when the Render function is executed, if the DOM is completely removed and rebuilt each time, there is a huge performance cost, because we know that the browser DOM is “expensive”, and when we update the DOM frequently, there will be some performance issues.

To solve this problem, Vue abstracts the browser’S DOM using JS objects. This abstraction is called the Virtual DOM. Each node of the Virtual DOM is defined as a VNode. When the render function is executed each time, Vue Diff compares the vNodes before and after the update to find as few real DOM nodes as possible that we need to update, and then only updates the nodes that need to be updated. This solves the performance problem caused by frequent DOM updates.

VNode

A VNode is a virtual representation of a real DOM node. Each component instance of a Vue is mounted with a $createElement function, from which all VNodes are created.

For example, create a div:

// declare render function
render: function (createElement) {
    // You can also create a VNode with this.$createElement
    return createElement('div'.'hellow world');
}
Hellow world
Copy the code

After the render function is executed, the vNodes are mapped according to the VNode Tree to generate the real DOM to complete the rendering of the view.

Diff

Diff compares old and new VNodes, and then makes minimal changes to the view based on the comparison, rather than redrawing the entire view to the new VNode to improve performance.

patch

The diff inside vue.js is called patch. Its DIff algorithm compares tree nodes of the same layer instead of searching the tree layer by layer, so the time complexity is only O(n), which is a fairly efficient algorithm.

The function sameVnode is used to determine whether the new and old nodes are the same: the key value and tag must be the same, etc., and return true, otherwise false.

Before patch, check whether the old and new Vnodes meet sameVnode(oldVnode, newVnode) condition, and then enter the process patchVnode. Otherwise, it will be judged as different nodes, and the old nodes will be removed and new nodes will be created.

patchVnode

The main function of patchVnode is to determine how to update child nodes.

  1. If both the old and new vNodes are static, have the same key (representing the same node), and the new VNode is clone or once (marked with the V-once attribute, rendering only once), then only the DOM and VNode need to be replaced.
  2. If both the old and new nodes have children, the diff operation is performed on the children to perform updateChildren, which is also the core of the DIff operation.
  3. If the old node has no children and the new node has children, clear the text content of the old DOM node and add children to the current DOM node.
  4. When a new node has no children and an old node has children, all children of that DOM node are removed.
  5. When both old and new nodes have no children, it is simply a text replacement.

updateChildren

The core of Diff is to compare the data of the new parent node to determine how to operate the child node. In the process of comparison, since the old child node has a reference to the current real DOM, the new child node is only a VNode array, so in the process of traversal, if the real DOM needs to be updated, The real DOM operation will be performed directly on the old child node, and the new parent node will be synchronized to the end of the traversal.

OldStartIdx oldEndIdx newStartIdx newEndIdx oldStartIdx newEndIdx oldStartIdx oldEndIdx newEndIdx oldStartIdx newEndIdx A node whose index is between oldStartIdx and oldEndIdx represents the traversed node in the laozi node, so a node less than oldStartIdx or greater than oldEndIdx represents the untraversed node. Similarly, in the new child node array, the node whose index is between newStartIdx and newEndIdx is the node that is traversed in the parent node, so less than newStartIdx or greater than newEndIdx is the node that is not traversed.

With each traversal, the distance between oldStartIdx and oldEndIdx and newStartIdx and newEndIdx gets closer to the middle. The loop ends when oldStartIdx > oldEndIdx or newStartIdx > newEndIdx.

In traversal, extract the Vnode corresponding to index 4:

  • OldStartIdx: oldStartVnode
  • OldEndIdx: oldEndVnode
  • NewStartIdx: newStartVnode
  • NewEndIdx: newEndVnode

During diff, if a key exists and sameVnode is satisfied, the DOM node is reused; otherwise, a new DOM node is created.

First, oldStartVnode and oldEndVnode are compared with newStartVnode and newEndVnode in pairs. There are altogether 4 comparison methods: 2*2=4.

Case 1: If oldStartVnode and newStartVnode meet sameVnode, oldStartVnode and newStartVnode make patchVnode, and oldStartIdx and newStartIdx move right.

Case 2: Similar to case 1, when oldEndVnode and newEndVnode satisfy sameVnode, oldEndVnode and newEndVnode make patchVnode, and oldEndIdx and newEndIdx move left.

Case 3: If oldStartVnode and newEndVnode satisfy sameVnode, oldStartVnode is already behind oldEndVnode. At this point, when oldStartVnode and newEndVnode conduct patchVnode, the real DOM node of oldStartVnode needs to be moved to the back of oldEndVnode, and oldStartIdx moves to the right and newEndIdx moves to the left.

Situation 4: Similar to case 3, when oldEndVnode and newStartVnode meet sameVnode, it indicates that oldEndVnode has preceded oldStartVnode. At this point, when oldEndVnode and newStartVnode conduct patchVnode, the real DOM node of oldEndVnode should be moved to the front of oldStartVnode, and oldStartIdx should be moved to the right and newEndIdx to the left.

If none of the four conditions is satisfied, the node that matches newStartVnode with sameVnode is searched between oldStartIdx and oldEndIdx. If there is one, the actual DOM of the matching node is moved to the front of oldStartVnode.If newStartVnode does not exist, newStartVnode is a new node. Create a new node before oldStartVnode.The loop ends when oldStartIdx > oldEndIdx or newStartIdx > newEndIdx, at which point we need to deal with vNodes that have not been traversed.

OldStartIdx > oldEndIdx indicates that the old node has been traversed, but the new node has not been traversed. In this case, the new node needs to be created after oldEndVnode.

When newStartIdx > newEndIdx, it indicates that the new node has been traversed, but the old node has not been traversed. In this case, all the old nodes that have not been traversed should be deleted.At this point, the child nodes are matched. Here is an example patch process diagram: conclusion

To borrow an official picture:

Vue. Js implements a set of declarative rendering engines and compiles declarative templates into rendering functions at Runtime or precompile time, mounted in Watcher, in rendering functions (touch), A responsive system collects as Dependency on observers using getter methods for responsive data and notifies all observers of updates using setter methods for responsive data. The Watcher will Trigger the component’s render function, which generates a new Virtual DOM Tree. At this point, Vue will Diff the new and old Virtual DOM trees to find out the real DOM to be operated and update it.