preface

Most of the article was written last August. At that time, I was very interested in VUE3, which led to the sorting and exploration. In fact, the content is completely out of date today, and even on nuggets you don’t see people writing about these partial principles, all talking about APIS.

Recently I was sorting out the front-end knowledge graph, and I took it out thinking of the previous sorting out related to Vue3 (I was lazy and took it out unchanged, but I will sort it out later and add some explanatory content).

Why upgrade to Vue3

  1. smaller
  • Composition Api: 13.5KB (31.94 KB for VUe2)
  • All Runtime: 22.5 KB (vue2:32kb)
  1. faster
  • SSR speed increased 2-3 times
  • Initial render/update can be up to twice as fast
  1. better

  • Update Performance improved by 1.3 to 2 times
  • The memory footprint was cut in half
  1. Are more likely to
  • Better TypeScript support
  • More friendly features and detection
  • .

What are the new features of Vue3

  1. Tree-shaking support (load on demand)
  2. Static tree lifting
  3. Static attribute promotion
  4. Virtual DOM Refactoring
  5. Slot optimization
  6. Suspense, Fragment, Teleport
  7. Support for TS (native Class Api and TSX)
  8. Composition API based on Proxy
  9. Custom Render

.

According to the need to load

Very useful functions can be loaded on demand, such as V-Model, Transition, etc

<div>{{ msg }}</div>
<input v-model="msg" />

// Compiled code
import { toDisplayString as _toDisplayString, createVNode as _createVNode, vModelText as _vModelText, withDirectives as _withDirectives, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from "vue"

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    _createVNode("div".null, _toDisplayString(_ctx.msg), 1 /* TEXT */),
    _withDirectives(_createVNode("input", {
      "onUpdate:modelValue": _cache[1] || (_cache[1] = $event => (_ctx.msg = $event))
    }, null.512 /* NEED_PATCH */), [
      [_vModelText, _ctx.msg]
    ])
  ], 64 /* STABLE_FRAGMENT */))}Copy the code
// In Vue2, initialize an application
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'

const app = new Vue({
  router,
  store
  render: h= > h(App)
})
app.$mount('#app')


// In Vue3, initialize an application
import { createApp } from 'vue'
import App from './app'
import router from './router'
import store from './store'

createApp(App).use(router).use(store).mount('#app')
Copy the code

Vue2 is initialized by new a Vue instance, whereas Vue3 is created by chain calls. This is tree-shaking, and modules that are not needed are not packaged. Properties on dynamic language objects cannot be handled by WebPack through object creation, nor can they be optimized for, for example, shortening property names through Uglify

Static ascension

<div>{{ msg }}</div>
<div class="msg2">{{ msg2 }}</div>
<div class="msg3">msg3</div>

// Compiled code
import { toDisplayString as _toDisplayString, createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from "vue"

const _hoisted_1 = { class: "msg2" }
const _hoisted_2 = /*#__PURE__*/_createVNode("div", { class: "msg3" }, "msg3", -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    _createVNode("div".null, _toDisplayString(_ctx.msg), 1 /* TEXT */),
    _createVNode("div", _hoisted_1, _toDisplayString(_ctx.msg2), 1 /* TEXT */),
    _hoisted_2
  ], 64 /* STABLE_FRAGMENT */))}Copy the code
<span>hello vue3</span>
<span>hello vue3</span>
<span>hello vue3</span>
<span>hello vue3</span>
<span>hello vue3</span>
<span>hello vue3</span>
<span>hello vue3</span>
<span>hello vue3</span>
<span>hello vue3</span>
<span>hello vue3</span>
<div>{{msg}}</div>

// Compiled code
import { createVNode as _createVNode, toDisplayString as _toDisplayString, createStaticVNode as _createStaticVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from "vue"

const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<span>hello vue3</span><span>hello vue3</span><span>hello vue3</span><span>hello vue3</span><span>hello vue3</span><span>hello vue3</span><span>hello vue3</span><span>hello vue3</span><span>hello vue3</span><span>hello vue3</span>".10)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    _hoisted_1,
    _createVNode("div".null, _toDisplayString(_ctx.msg), 1 /* TEXT */)].64 /* STABLE_FRAGMENT */))}Copy the code

Cache Handler

<div :class="msg1">{{ msg }}</div>
<div class="msg2">{{ msg2 }}</div>
<div class="msg3" @click="msgClickHandler">msg3</div>

// Compiled code
import { toDisplayString as _toDisplayString, createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from "vue"

const _hoisted_1 = { class: "msg2" }

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    _createVNode("div", { class: _ctx.msg1 }, _toDisplayString(_ctx.msg), 3 /* TEXT, CLASS */),
    _createVNode("div", _hoisted_1, _toDisplayString(_ctx.msg2), 1 /* TEXT */),
    _createVNode("div", {
      class: "msg3".onClick: _cache[1] || (_cache[1] = (. args) = >(_ctx.msgClickHandler(... args))) },"msg3")].64 /* STABLE_FRAGMENT */))}Copy the code

In Vue2, every time the render function runs out, the vNode bound event is a newly generated function, even though the internal code is the same. In Vue3, the incoming event automatically generates and cains an inline function in the cache, becoming a static node. This way, even if we write our own inline functions, we won’t have to repeat the rendering unnecessarily. React useCallback()

Free mode: Mainly used to reduce the number of objects created to reduce memory usage and improve performance. This type of design pattern is a structural pattern that provides a way to reduce the number of objects to improve the object structure required by the application. The meta-share pattern attempts to reuse existing objects of the same class, and if no match is found, new objects are created.

Patch Flag

<div :class="msg1" :id="msg1">{{ msg }}</div>
<div class="msg2">{{ msg2 }}</div>
<div class="msg3">msg3</div>

// Compiled code
import { toDisplayString as _toDisplayString, createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from "vue"

const _hoisted_1 = { class: "msg2" }
const _hoisted_2 = /*#__PURE__*/_createVNode("div", { class: "msg3" }, "msg3", -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    _createVNode("div", {
      class: _ctx.msg1,
      id: _ctx.msg1
    }, _toDisplayString(_ctx.msg), 11 /* TEXT, CLASS, PROPS */["id"]),
    _createVNode("div", _hoisted_1, _toDisplayString(_ctx.msg2), 1 /* TEXT */),
    _hoisted_2
  ], 64 /* STABLE_FRAGMENT */))}Copy the code
  const PatchFlagNames = {
    // Represents an element with a dynamic textContent
    [1 /* TEXT */] :`TEXT`.// represents an element with a dynamic class
    [2 /* CLASS */] :`CLASS`.// represents dynamic styles
    [4 /* STYLE */] :`STYLE`.// represents an element with non-class/style dynamic items.
    [8 /* PROPS */] :`PROPS`.// Represents the element of an item with a dynamic key, which is incompatible with the above three
    [16 /* FULL_PROPS */] :`FULL_PROPS`.// Represents an element with an event listener
    [32 /* HYDRATE_EVENTS */] :`HYDRATE_EVENTS`.// represents a fragment whose child order is invariant
    [64 /* STABLE_FRAGMENT */] :`STABLE_FRAGMENT`.// represents a fragment with keyed or partially keyed child elements.
    [128 /* KEYED_FRAGMENT */] :`KEYED_FRAGMENT`.// represents a fragment with a keyless binding
    [256 /* UNKEYED_FRAGMENT */] :`UNKEYED_FRAGMENT`.// represents an element with a dynamic slot
    [1024 /* DYNAMIC_SLOTS */] :`DYNAMIC_SLOTS`.// Indicates that only elements that are not attribute patches are required, such as ref or hooks
    [512 /* NEED_PATCH */] :`NEED_PATCH`[-1 /* HOISTED */] :`HOISTED`[-2 /* BAIL */] :`BAIL`
  };
Copy the code

Stack patchFlags if there are multiple patchFlags. TEXT=1, CLASS=2, PROPS=8, get 11; Then, after the patch function gets the flag, it respectively sums 1, 2, 4, and 8 bits. If the final result is not 0, it means that the dynamic attribute is contained.

000000001  1 text 
000000010  2 class
000000100  4 style
000001000  8 props

000001011  11

11 & 1  //true
11 & 2  //true
11 & 4  //false
11 & 8  //true
Copy the code

Compared with the React

ReactWent the other way, since the main question isdiffAnd it got stuck, soReactTook a similar CPU scheduling logic, thevdomThe tree becomes a linked list, done in the browser’s free timediffIf it is longer than 16ms and there is animation or user interaction to do, give control of the main process back to the browser and continue when it is idle. You’re actually doing diff in time that you don’t have to do before.

Time slice

The browser redraws the current page at regular intervals. The typical frequency is 60 times per second. That means the browser periodically redraws every 16 milliseconds, which we call a frame every 16 milliseconds. The main tasks of the browser in this frame are:

  1. Perform JS
  2. Calculate the Style
  3. Building a Layout model
  4. Paint Layer Style
  5. Composite computations render render results

If the total time of these six steps exceeds 16ms, the user may see the lag. If the task cannot complete within 50 milliseconds, then in order not to block the main thread, the task should relinquish control of the main thread so that the browser can work on other tasks and then come back to finish the unfinished task.

Vue3 has dropped support for time slicing

React supports
  • React’s virtual DOM manipulation (reconciliation) is inherently slow
  • React uses JSX to render functions that are more difficult to optimize than templates, which are easier to statically analyze.
  • React Hooks leave most of the component tree optimizations (preventing unnecessary re-rendering of child components) to the developers, a React application that uses Hooks would overrender by default
Why did Vue3 give up
  • React is intrinsically simpler, so the virtual DOM is faster
  • Through analyzing templates, a lot of pre-run compilation optimizations are made to reduce the basic overhead of virtual DOM operations. Benchmark shows that for a typical DOM code block, the ratio of dynamic to static content is about 1:4. Vue3’s native execution is even faster than Svelte’s, taking less than 1/10 of the CPU time of React, and time slicing only makes sense if the CPU is heavy.
  • Smart component tree optimization compiles slots into functions (to avoid repeated rendering of child elements) and automatically caches inline handles (to avoid repeated rendering of inline functions) through reactive tracing. Child components never need to be rerendered unless necessary. All this without any manual optimization by the developers.
  • Time slicing adds additional complexity, and Vue 3’s runtime is still only 1/4 the size of the current React + React DOM
  • Vue3 uses Proxy responsiveness + component internal VDOM + static markup to control the granularity of the task sufficiently that time-slice is not necessary
  • Time slicing specifically addresses issues that are more salient in React than other frameworks, while also introducing costs. For Vue 3, this trade-off doesn’t seem worth it

Slot optimization

In VUe2, when the data of the parent component is updated, re-rendering will be triggered and the patch of the parent component will be finally executed. During the patch process, when the component Vnode is encountered, prepatch of the old and new vnodes will be executed. This in turn executes the updateChildComponent, and if the child vNode has a slot, re-executes the forceUpdate() of the child, in which case it triggers a re-rendering of the child. Simply put, the slot is rerendered when the parent component is updated. Vue3 optimizes this scenario.

In Vue3, all compiler-generated slots will be functional and will only be called when the render function of the child component is called. This causes the dependencies in slot to be treated as dependencies of the child component, rather than the current parent component; This means: 1) When the contents of a slot change, only child components are rerendered; 2) When the parent component is re-rendered, there is no need for the child component to be re-rendered if the contents of the child component have not changed.

  1. When compiling statically, give oneComponentA dozenPatchFlagTag — yes or noDynamicSlot
  2. Encounter incomingslotThe component of itChildrenNot ordinaryvnodeArray, but oneslot functionMapping table, theseslot functionUsed for lazy generation in componentsslotIn thevnodes.
  3. In the child componentrenderFunction, call the correspondingslotGenerating the function, so thisslotThe properties in the function are represented by the current component instancetrack.

Suspense

React can render a nested component tree in memory before rendering it to the screen. It can detect asynchronous dependencies in the entire tree and render the tree to the screen only after rendering the entire tree’s asynchronous dependencies (resolve).

<Suspense> is an experimental feature and its API will likely change.
Copy the code

Fragment

React automatically adds a layer of virtual nodes to the template, eliminating the need to wrap the root element around it

<div>{{ msg }}</div>
<div>{{ msg2 }}</div>

// Compiled code
import { toDisplayString as _toDisplayString, createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from "vue"

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    _createVNode("div".null, _toDisplayString(_ctx.msg), 1 /* TEXT */),
    _createVNode("div".null, _toDisplayString(_ctx.msg2), 1 /* TEXT */)].64 /* STABLE_FRAGMENT */))}Copy the code

Teleport

Portal, a global component copied from React, provides an excellent way to render child nodes to DOM nodes that exist outside the parent component. Can be applied in popovers and other components that need to be mounted globally.

    <button @click="modalOpen = true">
        Open full screen modal! (With teleport!)
    </button>

    <teleport to="body">
      <div v-if="modalOpen" class="modal">
        <div>
          I'm a teleported modal! My parent is "body".
          <button @click="modalOpen = false">
            Close
          </button>
        </div>
      </div>
    </teleport>
Copy the code

Composition API

Imperative programming –> Functional programming

  • Better logic reuse and code organization
  • Better type derivation

deprecatedthisIt is supported by functional callsTypescript

Mixin’s problem:
  • Naming conflicts

The default merge policy for Vue components is that local options override mixin options (except for lifecycle hooks). When dealing with named properties across multiple components and mixins, it becomes increasingly difficult to write code. Once third-party mixins are added as NPM packages with their own named attributes, they can be particularly difficult because they can cause conflicts.

  • Unclear source

When there are multiple layers or mixins, the origin of the attributes called is unclear

  • Implicit reliance on

There is no hierarchical relationship between mixins and the components that use them. This means that components can use data properties defined in mixins, but mixins can also use data properties assumed to be defined in components. If we want to refactor a component and change the name of the variable required by the mixin, we will see nothing wrong when we look at the component. Linter won’t find it either, we’ll only see errors at run time.

Mixin patterns seem safe on the surface. However, sharing code by merging objects is an anti-pattern because it adds vulnerability to code and masks the ability to reason. The smartest part of the Composition API is that it allows Vue to share code relying on safeguards built into native JavaScript, such as passing variables to function and module systems.

For the most part, you should be fine sticking with the classic API. However, if you are going to reuse code, the Composition API is definitely superior.

Vue2 reactive implementation: Object.defineProperty

Simply put, intercepting an object and adding set and GET to its properties

Object. DefineProperty shortcomings:

  • Sometimes you can’t listen for array changes
  • Requires deep traversal and wastes memory
  • Support for Map, Set, WeakMap and WeakSet

Vue3 responsive implementation: Proxy

  • Reactive is roughly implemented
const toProxy = new WeakMap(a);// Store the proxied object
const toRaw = new WeakMap(a);// Store the proxied object

function reactive(target) {
    // Create a responsive object
    return createReactiveObject(target);
}

function isObject(target) {
    return typeof target === "object"&& target ! = =null;
}

function hasOwn(target,key){
    return target.hasOwnProperty(key);
}

function createReactiveObject(target) {
    if(! isObject(target)) {return target;
    }
    
    let observed = toProxy.get(target);
    if(observed){ // Determine whether the proxy is used
        return observed;
    }
    if(toRaw.has(target)){ // Determine whether to duplicate the proxy
        return target;
    }
    
    const handlers = {
        get(target, key, receiver) {
            / / value
            let res = Reflect.get(target, key, receiver);
            track(target,'get',key); // Rely on collection
            Vue2 recursively adds getters, setters, setters, getters, setters, getters, setters, setters, getters, setters, setters, getters, setters, setters, getters, setters, setters, getters, setters
            return isObject(res) ? reactive(res) : res;
        },
        set(target, key, value, receiver) {
            let oldValue = target[key];
            let hadKey = hasOwn(target,key);
            let result = Reflect.set(target, key, value, receiver);
            if(! hadKey){ trigger(target,'add',key); // Trigger add
            }else if(oldValue ! == value){ trigger(target,'set',key); // Trigger the change
            }
            return result;
        },
        deleteProperty(target, key) {
            / /...
            const result = Reflect.deleteProperty(target, key);
            returnresult; }};// Start the proxy
    observed = new Proxy(target, handlers);
    toProxy.set(target,observed);
    toRaw.set(observed,target); // create a mapping table
    return observed;
}
Copy the code
  • A rough implementation of effect
const activeReactiveEffectStack = []; // place the response effect

function effect(fn) {
   const effect = function() {
    // effect
    return run(effect, fn);
  };
  effect(); // Execute once
  return effect;
}

function run(effect, fn) {
    try {
      activeReactiveEffectStack.push(effect);
      return fn(); // Effect can be stored to the corresponding key property when fn executes
    } finally{ activeReactiveEffectStack.pop(effect); }}Copy the code
const targetMap = new WeakMap(a);function track(target,type,key){
    // Check for effect
    const effect = activeReactiveEffectStack[activeReactiveEffectStack.length-1];
    if(effect){
        let depsMap = targetMap.get(target);
        if(! depsMap){// Map does not exist
            targetMap.set(target,depsMap = new Map());
        }
        let dep = depsMap.get(target);
        if(! dep){// If not, set
            depsMap.set(key,(dep = new Set()));
        }
        if(! dep.has(effect)){ dep.add(effect);// Add effect to the dependency}}}Copy the code
function trigger(target, type, key) {
  const depsMap = targetMap.get(target);
  if(! depsMap) {return;
  }
  let effects = depsMap.get(key);
  if (effects) {
    effects.forEach(effect= > {
      effect();
    });
  }
  If the current type is incrementing an attribute, effect using the length of the array should also be executed
  if (type === "add") {
    let effects = depsMap.get("length");
    if (effects) {
      effects.forEach(effect= >{ effect(); }); }}}Copy the code

  • The default is lazy monitoring. In Vue2, any responsive data is monitored at startup time. If there is a large amount of data, there can be a significant performance cost at application startup. In Vue3, only the data used in the initial visible part of the application is monitored.
  • More accurate notification of changes. For example, in Vue2, forcing a new attribute through vue. set causes all watch functions that depend on this object to be executed once; In Vue3, only watch functions that depend on this specific property are notified.
  • Immutable monitoring object. We can create an “immutable” version of an object, a mechanism that can be used to freeze objects passed on component properties and Vuex state trees outside the mutation range.
  • Better debugging capability. By using the newrenderTrackedrenderTriggeredHook, which allows us to track exactly when a component is rerendered and when it is rerendered, and why.

Custom Render

With this API you can theoretically customize rendering functions for any platform and render VNode to different platforms, such as applets; You can copy @vue/ Runtime-dom to @vue/ Runtime-miniProgram, or @vue/ Runtime-canvas for a game. The arrival of this API will make it easier for projects such as Weex and NativeScript “render as native” to keep up to date with Vue.

Some discussion

  1. Should existing projects be upgraded
  • The new Composition API is compatible with Vue2, and you only need to introduce the @vue/ comation-API package into your project.
  • The last minor release of 2.X will become LTS and will continue to enjoy 18 months of bug and security fix updates after the 3.0 release.
  • Some of the current project of ecological library is facing huge upgrade, and after upgrading to fill in the pit, such as: the vue – the router, vuex, ElementUI/ViewUI/AntDesignVue, etc
  1. What happens to the component library when Element is not updated