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>
##ご覧のとおり、このコードには動的ノードが 1 つしかないため、実際には不必要な差分と走査が多数あります。これにより、
パフォーマンスが低下します。 vnode はテンプレート サイズに直接関係します。動的ノードの数はとは関係ありません。一部のコンポーネントのテンプレート全体に動的ノードが少数しかない場合、これらは走査はパフォーマンスの無駄です。上記の例では、理想的には、バインドされたメッセージの動的ノードの p タグを比較するだけで済みます。
Vue.js 3.0 コンパイル段階での静的テンプレートの分析を通じて、ブロック ツリー
がコンパイルおよび生成されます。
は、動的ノード命令に基づいてテンプレートを切り取るネストされたブロックです。各ブロック内のノード構造は固定されており、各ブロックには Array
が 1 つだけ必要です。それ自体に含まれる動的ノードを追跡します。 ブロック ツリー
の助けを借りて、Vue.js は、vnode 更新のパフォーマンスを、テンプレート全体のサイズに関連するものから、動的コンテンツの量に関連するものに改善しました。これは、非常に大きなパフォーマンスのブレークスルーです。 。
PatchFlag
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算法应该结束 }
ブロック ツリー左側 # #template コンパイル後、右側の
render 関数が生成されます。これには、_openBlock
、_createElementBlock
、_toDisplayString
が含まれます。 、 _createElementVNode
(createVnode
) およびその他の補助関数。 <div class="code" style="position:relative; padding:0px; margin:0px;"><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?.["key"],
__v_isVnode: true,
shapeFlag,
patchFlag
};
...
if (currentBlock && vnode.patchFlag > 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
? ""
: isObject(val)
? JSON.stringify(val)
: String(val);
}</pre><div class="contentsignin">ログイン後にコピー</div></div>
この時点で生成される vnode は次のとおりです:
dynamicChildren 属性がもう 1 つあります、動的ノード
span を収集します。 ノード diff 最適化戦略:
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 pass
patchBlockChildren
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, 'class', null, newProps.class, ...) } } // 存在动态 style 属性时 if (patchFlag & PatchFlags.STYLE) { hostPatchProp(el, 'style', 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 === 'class') { // 更新 class patchClass(el, nextValue) } else if (key === 'style') { // 更新 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('class'); // 如果不需要class直接移除 } else { el.className = nextValue } } function patchStyle(el, prevValue, nextValue = {}){ ... } function patchAttr(el, key, nextValue){ ... }
patchFlag
とdynamicChildren を最大限に活用します。
style の変更など、ローカルな変更のみがあると判断された場合は、
hostPatchProp のみが呼び出され、対応するパラメータ
style が渡されます。特定の更新を行うためのメソッド (
Targeted update);
dynamicChildren がある場合、
patchBlockChildren が比較と更新のために実行され、 は実行されません。毎回、props と子ノードの完全な比較と更新。
#静的プロモーション
静的プロモーションとは、静的なノードまたは属性をプロモートすることです。次のテンプレート:
<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 サイトの他の関連記事を参照してください。