Home >Web Front-end >Vue.js >What are the contents of vue3 compilation and optimization?

What are the contents of vue3 compilation and optimization?

王林
王林forward
2023-05-17 17:50:161334browse

vue3 compilation optimization includes: 1. Introducing patchFlag to mark dynamic content; during the compilation process, different labels will be marked according to different attribute types, thus realizing a fast diff algorithm . 2. Block Tree. 3. Static promotion is to promote static nodes or attributes. When there are more than 10 static nodes in a row, preprocessing will be stringified and merged into a continuous sequence of static nodes. When the cacheHandlers option is enabled, the function will be cached so that it can be called directly later.

This article mainly analyzes the optimization done in the Vue3.0 compilation stage, and how to use these optimization strategies to reduce the number of comparisons in the patch stage. Since the entire vnode tree of the component still needs to be traversed when the component is updated, such as the following template:

<template>
  <div id="container">
    <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>

The entire diff process is as shown in the figure:

What are the contents of vue3 compilation and optimization?

As you can see, because there is only one dynamic node in this code, there are many diffs and traversals that are actually unnecessary. This will cause the performance of vnode to be directly related to the template size, which is directly related to the template size. The number of dynamic nodes has nothing to do with . When there are only a few dynamic nodes in the entire template of some components, these traversals are a waste of performance. For the above example, ideally you only need to diff the p tag of the bound message dynamic node.

Vue.js 3.0 Through the analysis of the static template during the compilation stage, Block tree is compiled and generated.

Block tree is a nested block that cuts the template based on dynamic node instructions. The node structure inside each block is fixed, and each block only needs one Array to track the dynamic nodes contained in itself. With the help of Block tree, Vue.js improves vnode update performance from being related to the overall size of the template to being related to the amount of dynamic content. This is a very big performance breakthrough.

PatchFlag

Since the

diff algorithm cannot avoid useless comparison operations in the old and new virtual DOM, Vue. js 3.0 introduced patchFlag to mark dynamic content. During the compilation process, different identifiers will be marked according to different attribute types, thus realizing a fast diff algorithm. All enumeration types for PatchFlags are as follows:

export const enum PatchFlags {
  TEXT = 1, // 动态文本节点
  CLASS = 1 << 1, // 动态class
  STYLE = 1 << 2, // 动态style
  PROPS = 1 << 3, // 除了class、style动态属性
  FULL_PROPS = 1 << 4, // 有key,需要完整diff
  HYDRATE_EVENTS = 1 << 5, // 挂载过事件的
  STABLE_FRAGMENT = 1 << 6, // 稳定序列,子节点顺序不会发生变化
  KEYED_FRAGMENT = 1 << 7, // 子节点有key的fragment
  UNKEYED_FRAGMENT = 1 << 8, // 子节点没有key的fragment
  NEED_PATCH = 1 << 9, // 进行非props比较, ref比较
  DYNAMIC_SLOTS = 1 << 10, // 动态插槽
  DEV_ROOT_FRAGMENT = 1 << 11, 
  HOISTED = -1, // 表示静态节点,内容变化,不比较儿子
  BAIL = -2 // 表示diff算法应该结束
}

Block Tree

What are the contents of vue3 compilation and optimization?

# on the left ##template

After compilation, the render function on the right side will be generated, which contains _openBlock, _createElementBlock, _toDisplayString, _createElementVNode(createVnode) and other auxiliary functions. <pre class="brush:js;toolbar:false;">let currentBlock = null function _openBlock() { currentBlock = [] // 用一个数组来收集多个动态节点 } function _createElementBlock(type, props, children, patchFlag) { return setupBlock(createVnode(type, props, children, patchFlag)); } export function createVnode(type, props, children = null, patchFlag = 0) { const vnode = { type, props, children, el: null, // 虚拟节点上对应的真实节点,后续diff算法 key: props?.[&quot;key&quot;], __v_isVnode: true, shapeFlag, patchFlag }; ... if (currentBlock &amp;&amp; vnode.patchFlag &gt; 0) { currentBlock.push(vnode); } return vnode; } function setupBlock(vnode) { vnode.dynamicChildren = currentBlock; currentBlock = null; return vnode; } function _toDisplayString(val) { return isString(val) ? val : val == null ? &quot;&quot; : isObject(val) ? JSON.stringify(val) : String(val); }</pre>The vnode generated at this time is as follows:

What are the contents of vue3 compilation and optimization?The virtual node generated at this time has one more

dynamicChildren

attribute, which collects Dynamic node span.

Node diff optimization strategy:

We have analyzed before that when updating node elements in the

patch

stage, patchElement# will be executed ## function, let’s review its implementation:

const patchElement = (n1, n2) => { // 先复用节点、在比较属性、在比较儿子
  let el = n2.el = n1.el;
  let oldProps = n1.props || {}; // 对象
  let newProps = n2.props || {}; // 对象
  patchProps(oldProps, newProps, el);

  if (n2.dynamicChildren) { // 只比较动态元素
    patchBlockChildren(n1, n2);
  } else {
    patchChildren(n1, n2, el); // 全量 diff
  }
}
We analyzed this process in the previous chapter of component update. When analyzing the sub-node update part, we did not consider the optimization scenario at that time, so we only The scenario of full comparison and update was analyzed.

In fact, if this

vnode

is a

Block vnode, then we don’t need to pass patchChildren full comparison, we only need to passpatchBlockChildren Just compare and update the dynamic child nodes in Block. It can be seen that the performance has been greatly improved, from tree level comparison to linear structure comparison. Let’s take a look at its implementation:

const patchBlockChildren = (n1, n2) => {
  for (let i = 0; i < n2.dynamicChildren.length; i++) {
    patchElement(n1.dynamicChildren[i], n2.dynamicChildren[i])
  }
}

Attribute diff optimization strategy:

Next let’s take a look at the optimization strategy for attribute comparison:
const patchElement = (n1, n2) => { // 先复用节点、在比较属性、在比较儿子
  let el = n2.el = n1.el;
  let oldProps = n1.props || {}; // 对象
  let newProps = n2.props || {}; // 对象
  let { patchFlag, dynamicChildren } = n2
  
  if (patchFlag > 0) {
    if (patchFlag & PatchFlags.FULL_PROPS) { // 对所 props 都进行比较更新
      patchProps(el, n2, oldProps, newProps, ...)
    } else {
      // 存在动态 class 属性时
      if (patchFlag & PatchFlags.CLASS) {
        if (oldProps.class !== newProps.class) {
          hostPatchProp(el, &#39;class&#39;, null, newProps.class, ...)
        }
      }
      // 存在动态 style 属性时
      if (patchFlag & PatchFlags.STYLE) {
        hostPatchProp(el, &#39;style&#39;, oldProps.style, newProps.style, ...)
      }
      
      // 针对除了 style、class 的 props
      if (patchFlag & PatchFlags.PROPS) {
        const propsToUpdate = n2.dynamicProps!
        for (let i = 0; i < propsToUpdate.length; i++) {
          const key = propsToUpdate[i]
          const prev = oldProps[key]
          const next = newProps[key]
          if (next !== prev) {
            hostPatchProp(el, key, prev, next, ...)
          }
        }
      }
      if (patchFlag & PatchFlags.TEXT) { // 存在动态文本
        if (n1.children !== n2.children) {
          hostSetElementText(el, n2.children as string)
        }
      } 
    } else if (dynamicChildren == null) {
      patchProps(el, n2, oldProps, newProps, ...)
    }
  }
}

function hostPatchProp(el, key, prevValue, nextValue) {
  if (key === &#39;class&#39;) { // 更新 class 
    patchClass(el, nextValue)
  } else if (key === &#39;style&#39;) { // 更新 style
    patchStyle(el, prevValue, nextValue)
  } else if (/^on[^a-z]/.test(key)) {  // events  addEventListener
    patchEvent(el, key, nextValue);
  } else { // 普通属性 el.setAttribute
    patchAttr(el, key, nextValue);
  }
}

function patchClass(el, nextValue) {
  if (nextValue == null) {
    el.removeAttribute(&#39;class&#39;); // 如果不需要class直接移除
  } else {
    el.className = nextValue
  }
}

function patchStyle(el, prevValue, nextValue = {}){
  ...
}

function patchAttr(el, key, nextValue){
  ...
}

Summary:

vue3

will make full use of

patchFlag and dynamicChildren for optimization. If it is determined that there is only a local change, such as a style change, then hostPatchProp will only be called and the corresponding parameters style will be passed in to make a specific update (Targeted update); if there is dynamicChildren, patchBlockChildren will be executed for comparison and update, will not perform a full comparison and update of props and child nodes every time. The diagram is as follows:

What are the contents of vue3 compilation and optimization?Static promotion

Static promotion is to promote static nodes or attributes. Assume that there is the following template:
<div>
  <span>hello</span> 
  <span a=1 b=2>{{name}}</span>
  <a><span>{{age}}</span></a>
</div>

编译生成的 render 函数如下:

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", null, [
    _createElementVNode("span", null, "hello"),
    _createElementVNode("span", {
      a: "1",
      b: "2"
    }, _toDisplayString(_ctx.name), 1 /* TEXT */),
    _createElementVNode("a", null, [
      _createElementVNode("span", null, _toDisplayString(_ctx.age), 1 /* TEXT */)
    ])
  ]))
}

我们把模板编译成 render 函数是这个酱紫的,那么问题就是每次调用 render 函数都要重新创建虚拟节点。

开启静态提升 hoistStatic 选项后

const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", null, "hello", -1 /* HOISTED */)
const _hoisted_2 = {
  a: "1",
  b: "2"
}

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", null, [
    _hoisted_1,
    _createElementVNode("span", _hoisted_2, _toDisplayString(_ctx.name), 1 /* TEXT */),
    _createElementVNode("a", null, [
      _createElementVNode("span", null, _toDisplayString(_ctx.age), 1 /* TEXT */)
    ])
  ]))
}

预解析字符串化

静态提升的节点都是静态的,我们可以将提升出来的节点字符串化。 当连续静态节点超过 10 个时,会将静态节点序列化为字符串。

假如有如下模板:

<div>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
</div>

开启静态提升 hoistStatic 选项后

const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span>", 10)
const _hoisted_11 = [  _hoisted_1]

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", null, _hoisted_11))
}

函数缓存

假如有如下模板:

<div @click="event => v = event.target.value"></div>

编译后:

const _hoisted_1 = ["onClick"]

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", {
    onClick: event => _ctx.v = event.target.value
  }, null, 8 /* PROPS */, _hoisted_1))
}

每次调用 render 的时候要创建新函数,开启函数缓存 cacheHandlers 选项后,函数会被缓存起来,后续可以直接使用

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", {
    onClick: _cache[0] || (_cache[0] = event => _ctx.v = event.target.value)
  }))
}

The above is the detailed content of What are the contents of vue3 compilation and optimization?. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete