What are the main ways to improve the performance of Vue3.0?

Posted by Kaylub on Mon, 08 Nov 2021 21:26:58 +0100

1, Compilation phase

Reviewing Vue2, we know that each component instance corresponds to one   watcher   Instance, which records the data properties used during component rendering as dependencies. When the dependency changes and the setter is triggered, it will notify the watcher, so as to re render the associated components

Imagine a component structure as shown in the figure below

<template>
    <div id="content">
        <p class="text">static text</p>
        <p class="text">static text</p>
        <p class="text">{{ message }}</p>
        <p class="text">static text</p>
        ...
        <p class="text">static text</p>
    </div>
</template>

You can see that there is only one dynamic node inside the component, and the rest are static nodes, so there are many here   diff   And traversal are actually unnecessary, resulting in a waste of performance

Therefore, Vue3 is further optimized in the compilation phase. The main are as follows:

  • diff algorithm optimization

  • Static lift

  • Event listening cache

  • SSR optimization

diff algorithm optimization

vue3 adds static tags in diff algorithm compared with vue2

The function of this static tag is to add a flag tag to the place where changes will occur, and directly find the place for comparison when changes occur next time

In the following figure, the p tags of static nodes are not compared in the diff process, which further improves the performance

The static types are enumerated as follows

export const enum PatchFlags {
  TEXT = 1,//  Dynamic text node
  CLASS = 1 << 1,  //  two   Dynamic   class
  STYLE = 1 << 2,  //  four   Dynamic   style
  PROPS = 1 << 3,  //  eight   Dynamic properties, excluding class names and styles
  FULL_PROPS = 1 << 4,  //  sixteen   dynamic   Key, when   key   Changes require complete   diff   Algorithm comparison
  HYDRATE_EVENTS = 1 << 5,  //  thirty-two   Represents a node with an event listener
  STABLE_FRAGMENT = 1 << 6,   //  sixty-four   A that does not change the order of child nodes   Fragment
  KEYED_FRAGMENT = 1 << 7, //  one hundred and twenty-eight   have   key   Attribute   Fragment
  UNKEYED_FRAGMENT = 1 << 8, //  two hundred and fifty-six   No child nodes   key   of   Fragment
  NEED_PATCH = 1 << 9,   // 512
  DYNAMIC_SLOTS = 1 << 10,  //  dynamic   solt
  HOISTED = -1,  //  The special flag is a negative integer that will never be used as   diff
  BAIL = -2 //  A special flag refers to the difference algorithm
}

Static lift

In Vue3, elements that do not participate in updating will be statically promoted, created only once, and reused directly during rendering

In this way, repeated node creation is avoided. Large applications will benefit from this change, eliminating repeated creation operations and optimizing the memory occupation at run time

<span>Hello</span>

<div>{{ message }}</div>

Before static lifting

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

After static lifting

const _hoisted_1 = /*#__PURE__*/_createVNode("span", null, "Hello", -1 /* HOISTED */)

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

// Check the console for the AST

Static content_ hoisted_1 is placed in render   Function, each time you render, you just take  _ hoisted_ one   that will do

meanwhile  _ hoisted_ one   Got hit   PatchFlag  , The static flag value is - 1, and the special flag is a negative integer, indicating that it will never be used for Diff

Event listening cache

By default, the binding event behavior will be considered as dynamic binding, so it will track its changes every time

<div>
  <button @click = 'onClick'>Point me</button>
</div>

Event listener cache is not enabled

export const render = /*#__PURE__*/_withId(function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("button", { onClick: _ctx.onClick }, "Point me", 8 /* PROPS */, ["onClick"])
                                             //  PROPS=1<<3,//   eight  // Dynamic properties, but not class names and styles
  ]))
})

After the event listener cache is turned on

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("button", {
      onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args)))
    }, "Point me")
  ]))
}

The above findings show that after the cache is turned on, there is no static tag. That is to say, the diff algorithm will be used directly next time

SSR optimization

When the static content reaches a certain level, the createStaticVNode method will be used to generate a static node on the client. These static nodes will be directly innerHtml, so there is no need to create objects, and then render according to the objects

div>
 <div>
  <span>Hello</span>
 </div>
 ...  //  Many static properties
 <div>
  <span>{{ message }}</span>
 </div>
</div>

After compilation

import { mergeProps as _mergeProps } from "vue"
import { ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate } from "@vue/server-renderer"

export function ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {
  const _cssVars = { style: { color: _ctx.color }}
  _push(`<div${
    _ssrRenderAttrs(_mergeProps(_attrs, _cssVars))
  }><div><span>Hello</span>...<div><span>Hello</span><div><span>${
    _ssrInterpolate(_ctx.message)
  }</span></div></div>`)
}

2, Source volume

Compared with Vue2, the overall volume of Vue3 is smaller. In addition to removing some infrequent API s, the most important thing is Tree shanking

Any function, such as ref, reactivated, computed, etc., is packaged only when it is used. Unused modules are shaken off, and the overall volume of packaging becomes smaller

import { computed, defineComponent, ref } from 'vue';
export default defineComponent({
    setup(props, context) {
        const age = ref(18)

        let state = reactive({
            name: 'test'
        })

        const readOnlyAge = computed(() => age.value++) // 19

        return {
            age,
            state,
            readOnlyAge
        }
    }
});

3, Responsive system

Adopted in vue2   Define property to hijack the whole object, then deeply traverse all properties, add getter s and setter s to each property, and realize the response

vue3 uses proxy to rewrite the responsive system. Because proxy can listen to the whole object, it does not need deep traversal

  • You can listen for the addition of dynamic attributes

  • You can listen to the index of the array and the length attribute of the array

  • You can listen and delete attributes

The specific differences between the two API s will be described in more detail in the next article

Topics: Vue-cli