ホームページ  >  記事  >  ウェブフロントエンド  >  vue3のコンパイルと最適化の内容は何ですか?

vue3のコンパイルと最適化の内容は何ですか?

王林
王林転載
2023-05-17 17:50:161331ブラウズ

vue3 コンパイルの最適化には次のものが含まれます: 1. 動的コンテンツをマークするための patchFlag の導入; コンパイル プロセス中に、異なる属性タイプに従って異なるラベルがマークされるため、高速 diff アルゴリズムが実現します。 2. ブロックツリー。 3. 静的プロモーションとは、静的なノードまたは属性をプロモートすることです。連続して 10 個を超える静的ノードがある場合、前処理は文字列化され、静的ノードの連続シーケンスにマージされます。 cacheHandlers オプションが有効になっている場合、関数はキャッシュされるため、後で直接呼び出すことができます。

この記事では、主に Vue3.0 コンパイル段階で行われる最適化と、これらの最適化戦略を使用して パッチ# での比較の数を減らす方法を分析します。 ##ステージ。 コンポーネントが更新されるときは、次のテンプレートのようにコンポーネントの vnode ツリー全体をトラバースする必要があるため、

<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>

全体の差分プロセスは図に示すとおりです。

vue3のコンパイルと最適化の内容は何ですか?##ご覧のとおり、このコードには動的ノードが 1 つしかないため、実際には不必要な差分と走査が多数あります。これにより、

パフォーマンスが低下します。 vnode はテンプレート サイズに直接関係します。動的ノードの数は

とは関係ありません。一部のコンポーネントのテンプレート全体に動的ノードが少数しかない場合、これらは走査はパフォーマンスの無駄です。上記の例では、理想的には、バインドされたメッセージの動的ノードの p タグを比較するだけで済みます。

Vue.js 3.0

コンパイル段階での静的テンプレートの分析を通じて、ブロック ツリーがコンパイルおよび生成されます。

ブロック ツリー

は、動的ノード命令に基づいてテンプレートを切り取るネストされたブロックです。各ブロック内のノード構造は固定されており、各ブロックには Array が 1 つだけ必要です。それ自体に含まれる動的ノードを追跡します。 ブロック ツリーの助けを借りて、Vue.js は、vnode 更新のパフォーマンスを、テンプレート全体のサイズに関連するものから、動的コンテンツの量に関連するものに改善しました。これは、非常に大きなパフォーマンスのブレークスルーです。 。 PatchFlag

diff

アルゴリズムでは、新旧の仮想

DOM での無駄な比較操作を回避できないため、Vue.js 3.0 では、動的コンテンツをマークするための patchFlag が導入されました。コンパイル プロセス中に、さまざまな属性タイプに応じてさまざまな識別子がマークされるため、高速な diff アルゴリズムが実現します。 PatchFlags のすべての列挙型は次のとおりです:

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算法应该结束
}
ブロック ツリー

左側 # #templatevue3のコンパイルと最適化の内容は何ですか? コンパイル後、右側の

render

関数が生成されます。これには、_openBlock_createElementBlock_toDisplayString が含まれます。 、 _createElementVNode(createVnode) およびその他の補助関数。 <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>この時点で生成される vnode は次のとおりです:

この時点で生成される仮想ノードには

dynamicChildrenvue3のコンパイルと最適化の内容は何ですか? 属性がもう 1 つあります、動的ノード

span

を収集します。 ノード diff 最適化戦略:

以前に分析したところ、patch ステージでノード要素を更新すると、

patchElement# は次のようになります。 ## 関数を実行したので、その実装を確認してみましょう:

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
  }
}
このプロセスはコンポーネント更新の前の章で分析しましたが、サブノードの更新部分を分析するときに、その時点では最適化シナリオを考慮していませんでした。私たちだけが完全な比較と更新のシナリオを分析しました。 実際、この vnode

Block vnode

である場合、

patchChildren の完全な比較を渡す必要はありません。必要なのは、完全な比較だけです。 to passpatchBlockChildren Block 内の動的子ノードを比較して更新するだけです。 tree レベルの比較から線形構造の比較まで、パフォーマンスが大幅に向上していることがわかります。 その実装を見てみましょう:

const patchBlockChildren = (n1, n2) => {
  for (let i = 0; i < n2.dynamicChildren.length; i++) {
    patchElement(n1.dynamicChildren[i], n2.dynamicChildren[i])
  }
}
属性 diff 最適化戦略:

次に、属性比較の最適化戦略を見てみましょう。 :

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){
  ...
}
概要: vue3

は、最適化のために

patchFlag

dynamicChildren を最大限に活用します。 style の変更など、ローカルな変更のみがあると判断された場合は、hostPatchProp のみが呼び出され、対応するパラメータ style が渡されます。特定の更新を行うためのメソッド (Targeted update); dynamicChildren がある場合、patchBlockChildren が比較と更新のために実行され、 は実行されません。毎回、props と子ノードの完全な比較と更新#静的プロモーション

vue3のコンパイルと最適化の内容は何ですか?静的プロモーションとは、静的なノードまたは属性をプロモートすることです。次のテンプレート:

<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)
  }))
}

以上がvue3のコンパイルと最適化の内容は何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。