Vue.js パフォーマンス最適化 N 個のヒント (収集する価値あり)

Vue.js パフォーマンス最適化 N 個のヒント (収集する価値あり)

この記事は主に、米国で開催された第 19 回 Vue カンファレンスで Vue.js のコア メンバーである Guillaume Chau 氏が共有したトピック「9 つのパフォーマンスの秘密が明らかに」について言及しています。このトピックでは、9 つ​​の Vue.js パフォーマンス最適化手法が紹介されています。

彼が共有した PPT を見た後、関連するプロジェクトのソース コードも読みました。最適化の原則を深く理解した後、いくつかの最適化手法を日常業務に適用し、非常に良い結果を達成しました。

この共有は非常に実用的ですが、それを知っている人や注目している人は多くないようです。今のところ、このプロジェクトには数百個のスターしかありません。マスターが共有してから 2 年が経ちましたが、最適化のテクニックは時代遅れではありません。より多くの人々に実践的なスキルを理解して学んでもらうために、私は彼の共有にいくつかの二次加工を施し、最適化の原則を詳しく説明し、いくつかの拡張と拡張を行うことにしました。

この記事は主に Vue.js 2.x バージョンを対象としています。結局のところ、Vue.js 2.x は当分の間、私たちの仕事の主流バージョンであり続けるでしょう。

この記事を研究する際には、プロジェクトのソース コードを取得してローカルで実行し、最適化前後の効果の違いを確認することをお勧めします。

機能コンポーネント

最初のテクニックである関数コンポーネントについては、このライブサンプルをご覧ください。

最適化前のコンポーネント コードは次のとおりです。

<テンプレート>
  <div class="cell">
    <div v-if="値" クラス="on"></div>
    <セクション v-else クラス="オフ"></セクション>
  </div>
</テンプレート>

<スクリプト>
エクスポートデフォルト{
  プロパティ: ['値'],
}
</スクリプト>

最適化されたコンポーネント コードは次のとおりです。

<テンプレート機能>
  <div class="cell">
    <div v-if="props.value" class="on"></div>
    <セクション v-else クラス="オフ"></セクション>
  </div>
</テンプレート>

次に、親コンポーネントの最適化の前後で 800 個のコンポーネントをレンダリングし、各フレーム内のデータを変更してコンポーネントの更新をトリガーしました。Chrome パフォーマンス パネルを開いてパフォーマンスを記録し、次の結果が得られました。

最適化前:

最適化後:

これら 2 つの図を比較すると、最適化前のscript実行時間が最適化後よりも長いことがわかります。ご存知のように、JS エンジンはシングルスレッド操作メカニズムであり、JS スレッドは UI スレッドをブロックします。そのため、スクリプトの実行時間が長すぎると、レンダリングがブロックされ、ページがフリーズする原因になります。最適化されたscriptは実行時間が短くなるため、パフォーマンスが向上します。

では、関数コンポーネントを使用するとなぜ JS の実行時間が短くなるのでしょうか?これは、関数コンポーネントの実装原則から始まります。これは、渡されたコンテキスト データに基づいて DOM をレンダリングおよび生成できる関数と考えることができます。

機能コンポーネントは通常のオブジェクト型コンポーネントとは異なり、実際のコンポーネントとは見なされません。 patch処理中に、ノードがコンポーネントvnodeである場合、サブコンポーネントの初期化プロセスが再帰的に実行されます。一方、機能コンポーネントのrenderでは通常のvnodeが生成され、再帰的なサブコンポーネント プロセスは発生しないため、レンダリング コストは大幅に低くなります。

したがって、機能コンポーネントには、状態、レスポンシブ データ、ライフサイクル フック関数などが含まれません。これは、通常のコンポーネント テンプレート内の DOM の一部を切り取り、関数を通じてレンダリングすると考えることができます。これは、DOM レベルでの再利用の一種です。

子コンポーネントの分割

2 番目の手法であるサブコンポーネントの分割については、このオンラインの例を参照してください。

最適化前のコンポーネント コードは次のとおりです。

<テンプレート>
  <div :style="{ 不透明度: 数値 / 300 }">
    <div>{{ 重い() }}
  </div>
</テンプレート>

<スクリプト>
エクスポートデフォルト{
  プロパティ: ['数値'],
  メソッド: {
    重い () {
      定数n = 100000
      結果 = 0 とする
      (i = 0; i < n; i++ とします) {
        結果 += Math.sqrt(Math.cos(Math.sin(42)))
      }
      結果を返す
    }
  }
}
</スクリプト>

最適化されたコンポーネント コードは次のとおりです。

<テンプレート>
  <div :style="{ 不透明度: 数値 / 300 }">
    <チャイルドコンプ/>
  </div>
</テンプレート>

<スクリプト>
エクスポートデフォルト{
  コンポーネント:
    チャイルドコンプ: {
      メソッド: {
        重い () {
          定数n = 100000
          結果 = 0 とする
          (i = 0; i < n; i++ とします) {
            結果 += Math.sqrt(Math.cos(Math.sin(42)))
          }
          結果を返す
        },
      },
      レンダリング (h) {
        h('div', this.heavy()) を返します
      }
    }
  },
  プロパティ: ['数値']
}
</スクリプト>

次に、親コンポーネントの最適化の前後で 300 個のコンポーネントをレンダリングし、各フレーム内のデータを変更してコンポーネントの更新をトリガーします。Chrome パフォーマンス パネルを開いてパフォーマンスを記録すると、次の結果が得られます。

最適化前:

最適化後:

これら 2 つの画像を比較すると、最適化後のscriptの実行時間は最適化前よりも大幅に短縮され、パフォーマンス エクスペリエンスが向上していることがわかります。

では、なぜ違いがあるのでしょうか。最適化前のコンポーネントを見てみましょう。この例では、時間のかかるタスクをheavy関数でシミュレートしており、この関数はレンダリングごとに 1 回実行されるため、各コンポーネントのレンダリングでは JavaScript の実行に長い時間がかかります。

最適化された方法は、この時間のかかるタスクのheavy関数の実行ロジックを子コンポーネントChildCompにカプセル化することです。Vue はコンポーネントの粒度で更新されるため、各フレームで親コンポーネントはデータの変更によって再レンダリングされますが、 ChildComp内部に応答するデータの変更がないため再レンダリングされません。したがって、最適化されたコンポーネントはレンダリングごとに時間のかかるタスクを実行しないため、JavaScript の実行時間が自然に短縮されます。

ただし、この最適化方法については、いくつか異なる意見があります。詳細については、この問題をクリックしてください。このシナリオでは、サブコンポーネントに分割するよりも、計算プロパティを使用して最適化する方がよいと思います。計算プロパティ自体のキャッシュ機能のおかげで、時間のかかるロジックは最初のレンダリング時にのみ実行され、計算プロパティを使用するときにサブコンポーネントをレンダリングする追加のオーバーヘッドは発生しません。

実際の作業では、計算されたプロパティを使用してパフォーマンスを最適化するシナリオが多くあります。結局のところ、計算されたプロパティは、空間を時間と交換するという最適化の考え方を具体化したものでもあります。

ローカル変数

3 番目のトリックであるローカル変数については、このオンラインの例を確認してください。

最適化前のコンポーネント コードは次のとおりです。

<テンプレート>
  <div :style="{ opacity: start / 300 }">{{ 結果 }}</div>
</テンプレート>

<スクリプト>
エクスポートデフォルト{
  プロパティ: ['開始'],
  計算: {
    ベース(){
      戻る 42
    },
    結果 () {
      結果 = this.start とする
      (i = 0; i < 1000; i++ とします) {
        結果 += Math.sqrt(Math.cos(Math.sin(this.base))) + this.base * this.base + this.base + this.base * 2 + this.base * 3
      }
      結果を返す
    },
  },
}
</スクリプト>

最適化されたコンポーネント コードは次のとおりです。

<テンプレート>
  <div :style="{ opacity: start / 300 }">{{ 結果 }}</div>
</テンプレート>

<スクリプト>
エクスポートデフォルト{
  プロパティ: ['開始'],
  計算: {
    ベース(){
      戻る 42
    },
    結果 ({ ベース、開始 }) {
      結果 = 開始とする
      (i = 0; i < 1000; i++ とします) {
        結果 += Math.sqrt(Math.cos(Math.sin(base))) + base * base + base + base * 2 + base * 3
      }
      結果を返す
    },
  },
}
</スクリプト>

次に、親コンポーネントの最適化の前後で 300 個のコンポーネントをレンダリングし、各フレーム内のデータを変更してコンポーネントの更新をトリガーします。Chrome パフォーマンス パネルを開いてパフォーマンスを記録すると、次の結果が得られます。

最適化前:

最適化後:

これら 2 つの画像を比較すると、最適化後のscriptの実行時間は最適化前よりも大幅に短縮され、パフォーマンス エクスペリエンスが向上していることがわかります。

ここでの主な違いは、最適化前後のコンポーネントの計算されたプロパティresultの実装の違いです。最適化前のコンポーネントは計算プロセス中にthis.baseに何度もアクセスしますが、最適化後のコンポーネントは計算前にローカル変数baseを使用してthis.baseをキャッシュし、その後直接base変数にアクセスします。

では、なぜこの違いがパフォーマンスの違いを引き起こすのでしょうか? その理由は、 this.baseはレスポンシブなオブジェクトであるため、 this.baseにアクセスするたびにそのgetterトリガーされ、依存関係コレクションに関連するロジック コードが実行されるためです。同様のロジックが頻繁に実行されると、例のように、数百のコンポーネントが数百サイクルで更新され、各コンポーネントの計算computed再計算されるようにトリガーされ、依存関係の収集に関連するロジックが複数回実行されるため、パフォーマンスは当然低下します。

需要の観点から見ると、 this.base依存関係の収集を 1 回実行するだけで十分なので、そのgetter評価結果をローカル変数baseに返すだけで済みます。後でbaseに再度アクセスすると、 getterがトリガーされず、依存関係の収集ロジックが実行されないため、パフォーマンスが自然に向上します。

これは非常に実用的なパフォーマンス最適化手法です。なぜなら、多くの人が Vue.js プロジェクトを開発しているとき、変数を取得するときにthis.xxx直接記述することに慣れているからです。ほとんどの人はthis.xxxにアクセスした後で何が行われているかに注意を払わないからです。アクセス数が少ない場合、パフォーマンスの問題は顕著ではありませんが、例のシナリオのように、大きなループ内での複数のアクセスなど、アクセス数が増加すると、パフォーマンスの問題が発生します。

ZoomUI の Table コンポーネントのパフォーマンスを最適化していたとき、 render table bodyときにローカル変数の最適化手法を使用し、パフォーマンス比較のベンチマークを作成しました。1000 * 10 のテーブルをレンダリングすると、ZoomUI Table の更新されたデータの再レンダリングのパフォーマンスは、ElementUI の Table のほぼ 2 倍になりました。

v-show で DOM を再利用する

4 番目のヒントは、 v-showを使用して DOM を再利用することです。このオンライン例を確認できます。

最適化前のコンポーネント コードは次のとおりです。

<テンプレート機能>
  <div class="cell">
    <div v-if="props.value" クラス="on">
      <重い:n="10000"/>
    </div>
    <セクション v-else クラス="オフ">
      <重い:n="10000"/>
    </セクション>
  </div>
</テンプレート>

最適化されたコンポーネント コードは次のとおりです。

<テンプレート機能>
  <div class="cell">
    <div v-show="props.value" クラス="on">
      <重い:n="10000"/>
    </div>
    <セクション v-show="!props.value" クラス="オフ">
      <重い:n="10000"/>
    </セクション>
  </div>
</テンプレート>

次に、親コンポーネントの最適化の前後で 200 個のコンポーネントをレンダリングし、各フレーム内のデータを変更してコンポーネントの更新をトリガーします。Chrome パフォーマンス パネルを開いてパフォーマンスを記録すると、次の結果が得られます。

最適化前:

最適化後:

これら 2 つの画像を比較すると、最適化後のscriptの実行時間は最適化前よりも大幅に短縮され、パフォーマンス エクスペリエンスが向上していることがわかります。

最適化前と最適化後の主な違いは、コンポーネントの可視性を置き換えるために、 v-ifディレクティブの代わりにv-showディレクティブが使用されていることです。v v-showv-ifパフォーマンスが似ており、どちらもコンポーネントの可視性を制御しますが、内部実装にはまだ大きなギャップがあります。

v-ifディレクティブは、コンパイル フェーズ中に三項演算子にコンパイルされ、条件付きレンダリングに使用されます。たとえば、最適化前のコンポーネント テンプレートはコンパイルされて、次のレンダリング関数が生成されます。

関数レンダリング() {
  (これ) {
    _c('div', { を返す
      静的クラス: "セル"
    }, [(props.value) ? _c('div', {
      静的クラス: "オン"
    }, [_c('重い', {
      属性: {
        "n": 10000
      }
    })], 1) : _c('セクション', {
      静的クラス: "オフ"
    }, [_c('重い', {
      属性: {
        "n": 10000
      }
    })], 1)])
  }
}

条件props.valueの値が変化すると、対応するコンポーネントの更新がトリガーされます。 v-ifによってレンダリングされたノードの場合、古いノードと新しいノードのvnodeが一致していないため、コア diff アルゴリズムの比較プロセス中に、古いvnodeノードが削除され、新しいvnodeノードが作成されます。次に、新しいHeavyコンポーネントが作成され、 Heavyコンポーネントは、自身の初期化、 vnodeのレンダリング、 patchなどのプロセスを実行します。

したがって、 v-ifを使用すると、コンポーネントが更新されるたびに新しいHeavyサブコンポーネントが作成されます。更新されるコンポーネントが増えると、当然パフォーマンスが低下します。

v-showディレクティブを使用すると、最適化されたコンポーネント テンプレートがコンパイルされ、次のレンダリング関数が生成されます。

関数レンダリング() {
  (これ) {
    _c('div', { を返す
      静的クラス: "セル"
    }, [_c('div', {
      ディレクティブ: [{
        名前: "表示",
        生の名前: "v-show",
        値: (props.value)、
        式: "props.value"
      }],
      静的クラス: "オン"
    }, [_c('重い', {
      属性: {
        "n": 10000
      }
    })], 1), _c('セクション', {
      ディレクティブ: [{
        名前: "表示",
        生の名前: "v-show",
        値: (!props.value)、
        式: "!props.value"
      }],
      静的クラス: "オフ"
    }, [_c('重い', {
      属性: {
        "n": 10000
      }
    })], 1)])
  }
}

条件props.valueの値が変わると、対応するコンポーネントの更新がトリガーされます。 v-showによってレンダリングされるノードの場合、古い vnode と新しいvnodeは同じなので、常にpatchVnodeだけで済みます。では、DOM ノードはどのように表示および非表示になるのでしょうか。

patchVnodeプロセス中に、 v-show命令に対応するフック関数が内部的にupdatev-showにバインドされた値に従って、操作対象の DOM 要素のstyle.display値が設定され、可視性が制御されることがわかります。

そのため、常にDOMを削除して新しいDOMを作成するv-ifと比較して、 v-show既存のDOMの可視性を更新するだけです。そのため、 v-showのオーバーヘッドはv-ifのオーバーヘッドよりもはるかに小さくなります。内部のDOM構造が複雑になるほど、パフォーマンスの差は大きくなります。

ただし、 v-showv-ifよりもパフォーマンスに優れているのは、コンポーネントの更新フェーズです。初期化フェーズのみであれば、 v-ifのパフォーマンスはv-showよりも高くなります。その理由は、 v-show は両方のブランチをレンダリングし、 style.displayを通じて対応する DOM の可視性を制御するのに対し、 v-show 1 つのブランチのみをレンダリングするためです。

v-showを使用すると、ブランチ内のすべてのコンポーネントがレンダリングされ、対応するライフサイクルフック関数が実行されます。 v-ifを使用すると、ヒットしないブランチ内のコンポーネントはレンダリングされず、対応するライフサイクルフック関数は実行されません。

したがって、さまざまなシナリオで適切な指示を使用できるように、それらの原則と違いを理解する必要があります。

キープアライブ

5 番目のヒントは、 KeepAliveコンポーネントを使用して DOM をキャッシュすることです。このオンライン例を確認してください。

最適化前のコンポーネント コードは次のとおりです。

<テンプレート>
  <div id="アプリ">
    <ルータービュー/>
  </div>
</テンプレート>

最適化されたコンポーネント コードは次のとおりです。

<テンプレート>
  <div id="アプリ">
    <キープアライブ>
      <ルータービュー/>
    </キープアライブ>
  </div>
</テンプレート>

シンプル ページとヘビー ページを切り替えるボタンをクリックすると、異なるビューがレンダリングされ、ヘビー ページのレンダリングには非常に時間がかかります。 Chrome のパフォーマンス パネルを開いてパフォーマンスを記録し、最適化の前後に上記の操作を実行すると、次の結果が得られます。

最適化前:

最適化後:

これら 2 つの画像を比較すると、最適化後のscriptの実行時間は最適化前よりも大幅に短縮され、パフォーマンス エクスペリエンスが向上していることがわかります。

最適化されていないシナリオでは、ボタンをクリックしてルート ビューを切り替えるたびに、コンポーネントが再レンダリングされます。レンダリングされたコンポーネントは、コンポーネントの初期化、 renderpatchなどのプロセスを経ます。コンポーネントが複雑であったり、深くネストされている場合は、レンダリング全体に長い時間がかかります。

KeepAlive使用すると、最初のレンダリング後にKeepAliveでラップされたコンポーネントのvnodeと DOM がキャッシュされます。その後、コンポーネントが次回レンダリングされるときに、対応するvnodeと DOM がキャッシュから直接取得されてレンダリングされます。コンポーネントの初期化、 renderpatchなどの一連のプロセスを再度実行する必要がないため、 scriptの実行時間が短縮され、パフォーマンスが向上します。

ただし、 KeepAliveコンポーネントを使用すると、スペースと時間の最適化の考え方の典型的な適用であるキャッシュに多くのメモリが使用されるため、コストがかかります。

延期された機能

6 番目のヒントは、 Deferredコンポーネントを使用して、コンポーネントのレンダリングをバッチで遅延させることです。このオンライン サンプルを確認できます。

最適化前のコンポーネント コードは次のとおりです。

<テンプレート>
  <div class="deferred-off">
    <VueIcon アイコン="フィットネスセンター" クラス="巨大"/>

    <h2>私は重いページです</h2>

    <Heavy v-for="n in 8" :key="n"/>

    <重い class="super-heavy" :n="9999999"/>
  </div>
</テンプレート>

最適化されたコンポーネント コードは次のとおりです。

<テンプレート>
  <div class="deferred-on">
    <VueIcon アイコン="フィットネスセンター" クラス="巨大"/>

    <h2>私は重いページです</h2>

    <テンプレートv-if="defer(2)">
      <Heavy v-for="n in 8" :key="n"/>
    </テンプレート>

    <重い v-if="defer(3)" class="super-heavy" :n="9999999"/>
  </div>
</テンプレート>

<スクリプト>
'@/mixins/Defer' から Defer をインポートします。

エクスポートデフォルト{
  ミックスイン:
    延期()、
  ]、
}
</スクリプト>

シンプル ページとヘビー ページを切り替えるボタンをクリックすると、異なるビューがレンダリングされ、ヘビー ページのレンダリングには非常に時間がかかります。 Chrome のパフォーマンス パネルを開いてパフォーマンスを記録し、最適化の前後に上記の操作を実行すると、次の結果が得られます。

最適化前:

最適化後:

これら 2 つの画像を比較すると、最適化前、シンプル ページからヘビー ページに切り替えると、レンダリングが終了に近づいたときにページがまだシンプル ページとしてレンダリングされ、ページの遅延を感じさせることがわかります。最適化後、シンプル ページからヘビー ページに切り替えると、ヘビー ページは 1 回のレンダリングでページの先頭にレンダリングされ、ヘビー ページは段階的にレンダリングされます。

最適化前と最適化後の違いは、主に後者がDefer mixinを使用していることです。どのように動作するか見てみましょう。

デフォルト関数をエクスポートする(カウント = 10){
  戻る {
    データ () {
      戻る {
        表示優先度: 0
      }
    },

    マウントされた(){
      this.runDisplayPriority()
    },

    メソッド: {
      実行ディスプレイ優先度() {
        定数ステップ = () => {
          リクエストアニメーションフレーム(() => {
            this.displayPriority++
            if (this.displayPriority < count) {
              ステップ()
            }
          })
        }
        ステップ()
      },

      延期(優先度){
        this.displayPriority >= 優先度を返す
      }
    }
  }
}

Deferの主なアイデアは、コンポーネントのレンダリングを複数回に分割することです。内部的にdisplayPriority変数を維持し、 requestAnimationFrameを通じて各フレームのレンダリングごとにcountまで増加させます。次に、 Defer mixinを使用するコンポーネント内で、 v-if="defer(xxx)"を使用して、 displayPriorityxxxに増加したときに特定のブロックのレンダリングを制御できます。

レンダリングに時間のかかるコンポーネントがある場合は、プログレッシブレンダリングにDeferred使用することをお勧めします。これにより、JS 実行時間が長いためにrenderが停止する現象を回避できます。

タイムスライス

7 番目のヒントは、 Time slicingスライス テクノロジを使用することです。このオンラインの例を確認してください。

最適化前のコードは次のとおりです。

fetchItems ({ commit }, { items }) {
  コミット('clearItems')
  コミット('addItems', items)
}

最適化されたコードは次のとおりです。

fetchItems ({ commit }, { items, splitCount }) {
  コミット('clearItems')
  const キュー = 新しいジョブキュー()
  分割配列(アイテム、分割数).forEach(
    チャンク => キュー.addJob(完了 => {
      // タイムスライスでデータを送信 requestAnimationFrame(() => {
        コミット('addItems', チャンク)
        終わり()
      })
    })
  )
  キューの開始を待ちます()
}

まず、 Genterate itemsボタンをクリックして 10,000 個の偽データを作成し、次にCommit itemsボタンをクリックして、 Time-slicingそれぞれオンとオフにしてデータを送信します。Chrome パフォーマンス パネルを開いてパフォーマンスを記録すると、次の結果が得られます。

最適化前:

最適化後:

これら 2 つの画像を比較すると、最適化前のscript実行時間の合計が最適化後よりも短いことがわかります。ただし、実際の視覚的な体験から見ると、最適化前に [送信] ボタンをクリックすると、ページが約 1.2 秒間停止します。最適化後は、ページが完全に停止することはありませんが、レンダリングの遅延の感覚はまだ残ります。

では、最適化前にページがフリーズするのはなぜでしょうか?一度に送信されたデータが多すぎるため、内部の JS 実行時間が長くなりすぎて、UI スレッドがブロックされ、ページがフリーズする原因になりました。

最適化後も、データを 1,000 項目の粒度で分割したため、ページにはまだ若干の遅延があります。この場合、コンポーネントを再レンダリングする必要がまだあります。fps がわずか 12 程度で、若干の遅延が発生していることがわかりました。通常、ページの fps が 60 に達すれば、ページは非常にスムーズになります。データを 100 個に分割すると、fps は基本的に 50 以上に達します。ページのレンダリングはスムーズになりますが、10,000 個のデータを完了するための合計送信時間は依然として長くなります。

Time slicing技術を使用すると、ページのフリーズを回避できます。通常、このような時間のかかるタスクを処理するときは、 loading効果を追加します。この例では、 loading animationをオンにしてからデータを送信できます。比較すると、最適化前は、一度に送信されるデータが多すぎるため、JS が長時間実行され、UI スレッドがブロックされ、 loadingアニメーションが表示されないことがわかりました。最適化後は、データを複数のタイムスライスに分割したため、単一の JS 実行時間が短くなり、 loadingアニメーションが表示されるようになりました。

ここで注意すべき点は、 requestAnimationFrame API を使用してタイム スライスを分割しているにもかかわらず、 requestAnimationFrame自体を使用してもフル フレームの操作が保証されないことです。requestAnimationFrame requestAnimationFrame 、ブラウザーが再描画されるたびに、渡された対応するコールバック関数が実行されることを保証します。フル フレームを保証する唯一の方法は、JS を 1 ティックあたり 17 ミリ秒以内に実行することです。

非反応データ

8 番目のヒントは、 Non-reactive dataを使用することです。このオンライン例を確認できます。

最適化前のコードは次のとおりです。

定数データ = items.map(
  アイテム => ({
    id: uid++,
    データ: 項目、
    投票: 0
  })
)

最適化されたコードは次のとおりです。

定数データ = items.map(
  アイテム => 最適化アイテム(アイテム)
)

関数optimizeItem(item){
  定数項目データ = {
    id: uid++,
    投票: 0
  }
  Object.defineProperty(itemData, 'データ', {
    // 非反応としてマーク
    設定可能: false、
    値: アイテム
  })
  アイテムデータを返す
}

引き続き前の例を使用して、最初にGenterate itemsボタンをクリックして 10,000 個の偽データを作成し、次にCommit itemsボタンをクリックして、 Partial reactivityをそれぞれオンとオフにしてデータを送信します。Chrome のパフォーマンス パネルを開いてパフォーマンスを記録すると、次の結果が得られます。

最適化前:

最適化後:

これら 2 つの画像を比較すると、最適化後のscriptの実行時間は最適化前よりも大幅に短縮され、パフォーマンス エクスペリエンスが向上していることがわかります。

この違いの理由は、内部的にデータが送信されると、新しく送信されたデータはデフォルトでレスポンシブとして定義されるためです。データのサブ属性がオブジェクト形式の場合、サブ属性も再帰的にレスポンシブになります。そのため、大量のデータが送信されると、このプロセスは時間のかかるプロセスになります。

最適化後、新しく送信されたデータ内のオブジェクト属性data configurableからfalseに手動で変更しました。このようにすると、 walk中にObject.keys(obj)を介してオブジェクト属性配列を取得するときに、 data無視され、 data属性にdefineReactiveが設定されません。 dataオブジェクトを指しているため、これにより再帰的な応答ロジックが削減され、この部分のパフォーマンス損失が削減されることに相当します。データ量が多くなればなるほど、この最適化の効果は明ら​​かになります。

実際、同様の最適化方法はたくさんあります。たとえば、コンポーネントで定義する一部のデータは、必ずしもdataで定義する必要はありません。一部のデータはテンプレートでは使用されず、その変更を監視する必要もありません。このデータをコンポーネントのコンテキストで共有したいだけです。この時点では、このデータをコンポーネント インスタンスthisにマウントするだけで済みます。次に例を示します。

エクスポートデフォルト{
  作成された() {
    this.scroll = null
  },
  マウント() {
    this.scroll = 新しい BScroll(this.$el)
  }
}

この方法ではscrollオブジェクトがレスポンシブ オブジェクトではない場合でも、コンポーネント コンテキストで共有できます。

仮想スクロール

9 番目のヒントは、 Virtual scrollingを使用することです。このオンラインの例を確認できます。

最適化前のコンポーネントのコードは次のとおりです。

<div class="items no-v">
  <FetchItemViewFunctional
    v-for="アイテムのアイテム"
    :key="アイテムID"
    :item="アイテム"
    @vote="投票アイテム(アイテム)"
  />
</div>

最適化されたコードは次のとおりです。

<リサイクルスクロール
  クラス="アイテム"
  :items="アイテム"
  :item-size="24"
>
  <テンプレート v-slot="{ アイテム }">
    <アイテムビューの取得
      :item="アイテム"
      @vote="投票アイテム(アイテム)"
    />
  </テンプレート>
</リサイクル スクロール>

引き続き前の例を使用して、 View listを開き、 Genterate itemsボタンをクリックして 10,000 個の偽データを作成する必要があります (オンラインの例では最大 1,000 個のデータしか作成できないことに注意してください。実際には、1,000 個のデータでは最適化の効果を十分に反映できないため、ソース コードの制限を変更し、ローカルで実行して 10,000 個のデータを作成しました)。次に、 UnoptimizedおよびRecycleScrollerのケースでCommit itemsボタンをクリックしてデータを送信し、ページをスクロールして、Chrome のパフォーマンス パネルを開いてパフォーマンスを記録します。次の結果が得られます。

最適化前:

最適化後:

この 2 つの画像を比較すると、最適化されていない場合、10,000 データの fps は、スクロール時に 1 桁に過ぎず、スクロールしない場合は数十に過ぎないことがわかります。これは、最適化されていないシナリオではレンダリングされる DOM が多すぎるため、レンダリング自体に大きな負荷がかかるためです。最適化後、データ項目が 10,000 個あっても、スクロールする場合は fps が 30 を超え、スクロールしない場合は 60 フレームに達することができます。

この違いの理由は、仮想スクロールの実装方法にあります。仮想スクロールでは、ビューポート内の DOM のみがレンダリングされます。この方法では、レンダリングされる DOM の合計数は非常に少なくなり、パフォーマンスは当然大幅に向上します。

仮想スクロール コンポーネントも Guillaume Chau によって作成されました。興味のある学生は、そのソース コードの実装を学習できます。その基本原理は、スクロール イベントをリッスンし、表示する必要のある DOM 要素を動的に更新し、ビュー内でのそれらの変位を計算することです。

仮想スクロール コンポーネントは、スクロール プロセス中にリアルタイムで計算する必要があるため、コストがかからないわけではなく、一定のscript実行コストが発生します。したがって、リスト内のデータ量がそれほど多くない場合は、通常のスクロールを使用するだけで十分です。

要約する

この記事を通じて、Vue.js のパフォーマンス最適化手法を 9 つ学び、実際の開発プロジェクトに応用していただければ幸いです。上記の手法に加えて、画像の遅延読み込み、コンポーネントの遅延読み込み、非同期コンポーネントなどのパフォーマンス最適化手法もよく使用されます。

パフォーマンスを最適化する前に、パフォーマンスのボトルネックがどこにあるかを分析して、適切な対策を講じる必要があります。さらに、パフォーマンスの最適化にはデータのサポートが必要です。パフォーマンスの最適化を行う前に、最適化前のデータを収集して、最適化後のデータの比較を通じて最適化の効果を確認できるようにする必要があります。

今後の開発では、要件を満たすだけで満足するのではなく、コードの各行がパフォーマンスにどのような影響を与える可能性があるかを考えて記述していただければ幸いです。

参考文献

[1] vue-9-perf-secretsスライド: https://slides.com/akryum/vueconfus-2019

[2] vue-9-perf-secrets 共有スピーチビデオ: https://www.vuemastery.com/conference/vueconf-us-2019/9-performance-secrets-revealed/

[3] vue-9-perf-secretsプロジェクトのソースコード: https://github.com/Akryum/vue-9-perf-secrets

[4] vue-9-perf-secretsオンラインデモアドレス: https://vue-9-perf-secrets.netlify.app/

[5] vue-9-perf-secrets ディスカッション問題: https://github.com/Akryum/vue-9-perf-secrets/issues/1

[6] vue-virtual-scrollerプロジェクトのソースコード: https://github.com/Akryum/vue-virtual-scroller

これで、Vue.js のパフォーマンス最適化のヒント 9 つ (収集する価値があります) に関するこの記事は終了です。Vue.js のパフォーマンス最適化のヒントの詳細については、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • Vueの初期化パフォーマンスの最適化に関する簡単な説明
  • Vue のパフォーマンス最適化に関する簡単な説明: 配列を深く掘り下げる
  • Vue のパフォーマンスを最適化する方法
  • Vue 開発のための 12 のパフォーマンス最適化のヒント

<<:  LINUX でポートが占有されているかどうかを確認する方法

>>:  MySQLの行数カウントに関する簡単な説明

推薦する

MySQL インデックス プッシュダウンの詳細

目次1. 左端接頭辞原則2. 表に戻る3. インデックスプッシュダウン序文:インデックス プッシュダ...

HTML でハイパーリンク タグを使用するチュートリアル

ウェブサイトのさまざまな HTML ドキュメントはハイパーリンクを通じて相互に接続され、一貫性のある...

Linuxでのcrontabの使い方と注意点の詳しい説明

Crontab は定期的な実行を設定するために使用されるコマンドです。そのデーモン プロセスは cr...

MLSQL スタックでストリームのデバッグを簡単にする方法

序文クラスメートが MLSQL Stack のストリーミング サポートを調査しています。そこで、フロ...

nginx アンチホットリンクおよびアンチクローラー設定の詳細な説明

新しい設定ファイルを作成します (たとえば、nginx インストール ディレクトリの下の conf ...

iframe パラメータの説明と例

<iframe src=”test.jsp” width=”100″ height=”50″ ...

Vue で配列をクリアするいくつかの方法 (要約)

目次1. はじめに2. データを消去するいくつかの方法2.1 ref() の使用2.2 スライスの使...

Vueはルールを使用してフォームフィールドの検証を実装します

Vue でフォーム フィールドを記述および検証する方法は多数あります。このブログでは、より一般的に使...

WeChatミニプログラムマップの使い方を詳しく解説

この記事の例では、WeChatアプレットマップで使用される具体的な実装コードを参考までに共有していま...

CSSボックスモデルの紹介を読めば、混乱することはなくなるでしょう

Web デザインでよく耳にするプロパティ名: content、padding、border、marg...

SQL における参照整合性の詳細な説明 (1 対 1、1 対多、多対多)

1. 参照整合性参照整合性とは、主に外部キー制約を使用した複数のテーブル間の設計を指します。複数テ...

ウェブページ制作TDは隠し表示もオーバーフロー可能

おそらく、この記事にこのようなタイトルを付けると、誰かがこう尋ねるでしょう。「なぜまだテーブルに注目...

HTML の基本 - CSS スタイルシート、スタイル属性、フォーマット、レイアウトの詳細

1.位置:固定一部の Web サイトの右下隅にあるポップアップ ウィンドウなどの、ブラウザーを基準と...

Vue は div の高さをドラッグ可能にします

この記事では、divのドラッグ可能な高さを実現するためのVueの具体的なコードを参考までに共有します...