この記事は主に、米国で開催された第 19 回 Vue カンファレンスで Vue.js のコア メンバーである Guillaume Chau 氏が共有したトピック「9 つのパフォーマンスの秘密が明らかに」について言及しています。このトピックでは、9 つの Vue.js パフォーマンス最適化手法が紹介されています。 彼が共有した PPT を見た後、関連するプロジェクトのソース コードも読みました。最適化の原則を深く理解した後、いくつかの最適化手法を日常業務に適用し、非常に良い結果を達成しました。 この共有は非常に実用的ですが、それを知っている人や注目している人は多くないようです。今のところ、このプロジェクトには数百個のスターしかありません。マスターが共有してから 2 年が経ちましたが、最適化のテクニックは時代遅れではありません。より多くの人々に実践的なスキルを理解して学んでもらうために、私は彼の共有にいくつかの二次加工を施し、最適化の原則を詳しく説明し、いくつかの拡張と拡張を行うことにしました。
この記事を研究する際には、プロジェクトのソース コードを取得してローカルで実行し、最適化前後の効果の違いを確認することをお勧めします。 機能コンポーネント最初のテクニックである関数コンポーネントについては、このライブサンプルをご覧ください。 最適化前のコンポーネント コードは次のとおりです。 <テンプレート> <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 つの図を比較すると、最適化前の では、関数コンポーネントを使用するとなぜ JS の実行時間が短くなるのでしょうか?これは、関数コンポーネントの実装原則から始まります。これは、渡されたコンテキスト データに基づいて DOM をレンダリングおよび生成できる関数と考えることができます。 機能コンポーネントは通常のオブジェクト型コンポーネントとは異なり、実際のコンポーネントとは見なされません。 したがって、機能コンポーネントには、状態、レスポンシブ データ、ライフサイクル フック関数などが含まれません。これは、通常のコンポーネント テンプレート内の 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 つの画像を比較すると、最適化後の では、なぜ違いがあるのでしょうか。最適化前のコンポーネントを見てみましょう。この例では、時間のかかるタスクを 最適化された方法は、この時間のかかるタスクの ただし、この最適化方法については、いくつか異なる意見があります。詳細については、この問題をクリックしてください。このシナリオでは、サブコンポーネントに分割するよりも、計算プロパティを使用して最適化する方がよいと思います。計算プロパティ自体のキャッシュ機能のおかげで、時間のかかるロジックは最初のレンダリング時にのみ実行され、計算プロパティを使用するときにサブコンポーネントをレンダリングする追加のオーバーヘッドは発生しません。 実際の作業では、計算されたプロパティを使用してパフォーマンスを最適化するシナリオが多くあります。結局のところ、計算されたプロパティは、空間を時間と交換するという最適化の考え方を具体化したものでもあります。 ローカル変数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 つの画像を比較すると、最適化後の ここでの主な違いは、最適化前後のコンポーネントの計算されたプロパティ では、なぜこの違いがパフォーマンスの違いを引き起こすのでしょうか? その理由は、 需要の観点から見ると、 これは非常に実用的なパフォーマンス最適化手法です。なぜなら、多くの人が Vue.js プロジェクトを開発しているとき、変数を取得するときに ZoomUI の Table コンポーネントのパフォーマンスを最適化していたとき、 v-show で DOM を再利用する4 番目のヒントは、 最適化前のコンポーネント コードは次のとおりです。 <テンプレート機能> <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 つの画像を比較すると、最適化後の 最適化前と最適化後の主な違いは、コンポーネントの可視性を置き換えるために、 関数レンダリング() { (これ) { _c('div', { を返す 静的クラス: "セル" }, [(props.value) ? _c('div', { 静的クラス: "オン" }, [_c('重い', { 属性: { "n": 10000 } })], 1) : _c('セクション', { 静的クラス: "オフ" }, [_c('重い', { 属性: { "n": 10000 } })], 1)]) } } 条件 したがって、 関数レンダリング() { (これ) { _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)]) } } 条件 そのため、常にDOMを削除して新しいDOMを作成する ただし、 したがって、さまざまなシナリオで適切な指示を使用できるように、それらの原則と違いを理解する必要があります。 キープアライブ5 番目のヒントは、 最適化前のコンポーネント コードは次のとおりです。 <テンプレート> <div id="アプリ"> <ルータービュー/> </div> </テンプレート> 最適化されたコンポーネント コードは次のとおりです。 <テンプレート> <div id="アプリ"> <キープアライブ> <ルータービュー/> </キープアライブ> </div> </テンプレート> シンプル ページとヘビー ページを切り替えるボタンをクリックすると、異なるビューがレンダリングされ、ヘビー ページのレンダリングには非常に時間がかかります。 Chrome のパフォーマンス パネルを開いてパフォーマンスを記録し、最適化の前後に上記の操作を実行すると、次の結果が得られます。 最適化前: 最適化後: これら 2 つの画像を比較すると、最適化後の 最適化されていないシナリオでは、ボタンをクリックしてルート ビューを切り替えるたびに、コンポーネントが再レンダリングされます。レンダリングされたコンポーネントは、コンポーネントの初期化、 ただし、 延期された機能6 番目のヒントは、 最適化前のコンポーネント コードは次のとおりです。 <テンプレート> <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 回のレンダリングでページの先頭にレンダリングされ、ヘビー ページは段階的にレンダリングされます。 最適化前と最適化後の違いは、主に後者が デフォルト関数をエクスポートする(カウント = 10){ 戻る { データ () { 戻る { 表示優先度: 0 } }, マウントされた(){ this.runDisplayPriority() }, メソッド: { 実行ディスプレイ優先度() { 定数ステップ = () => { リクエストアニメーションフレーム(() => { this.displayPriority++ if (this.displayPriority < count) { ステップ() } }) } ステップ() }, 延期(優先度){ this.displayPriority >= 優先度を返す } } } } レンダリングに時間のかかるコンポーネントがある場合は、プログレッシブレンダリングに タイムスライス7 番目のヒントは、 最適化前のコードは次のとおりです。 fetchItems ({ commit }, { items }) { コミット('clearItems') コミット('addItems', items) } 最適化されたコードは次のとおりです。 fetchItems ({ commit }, { items, splitCount }) { コミット('clearItems') const キュー = 新しいジョブキュー() 分割配列(アイテム、分割数).forEach( チャンク => キュー.addJob(完了 => { // タイムスライスでデータを送信 requestAnimationFrame(() => { コミット('addItems', チャンク) 終わり() }) }) ) キューの開始を待ちます() } まず、 最適化前: 最適化後: これら 2 つの画像を比較すると、最適化前の では、最適化前にページがフリーズするのはなぜでしょうか?一度に送信されたデータが多すぎるため、内部の JS 実行時間が長くなりすぎて、UI スレッドがブロックされ、ページがフリーズする原因になりました。 最適化後も、データを 1,000 項目の粒度で分割したため、ページにはまだ若干の遅延があります。この場合、コンポーネントを再レンダリングする必要がまだあります。fps がわずか 12 程度で、若干の遅延が発生していることがわかりました。通常、ページの fps が 60 に達すれば、ページは非常にスムーズになります。データを 100 個に分割すると、fps は基本的に 50 以上に達します。ページのレンダリングはスムーズになりますが、10,000 個のデータを完了するための合計送信時間は依然として長くなります。
非反応データ8 番目のヒントは、 最適化前のコードは次のとおりです。 定数データ = items.map( アイテム => ({ id: uid++, データ: 項目、 投票: 0 }) ) 最適化されたコードは次のとおりです。 定数データ = items.map( アイテム => 最適化アイテム(アイテム) ) 関数optimizeItem(item){ 定数項目データ = { id: uid++, 投票: 0 } Object.defineProperty(itemData, 'データ', { // 非反応としてマーク 設定可能: false、 値: アイテム }) アイテムデータを返す } 引き続き前の例を使用して、最初に 最適化前: 最適化後: これら 2 つの画像を比較すると、最適化後の この違いの理由は、内部的にデータが送信されると、新しく送信されたデータはデフォルトでレスポンシブとして定義されるためです。データのサブ属性がオブジェクト形式の場合、サブ属性も再帰的にレスポンシブになります。そのため、大量のデータが送信されると、このプロセスは時間のかかるプロセスになります。 最適化後、新しく送信されたデータ内のオブジェクト属性 実際、同様の最適化方法はたくさんあります。たとえば、コンポーネントで定義する一部のデータは、必ずしも エクスポートデフォルト{ 作成された() { this.scroll = null }, マウント() { this.scroll = 新しい BScroll(this.$el) } } この方法では 仮想スクロール9 番目のヒントは、 最適化前のコンポーネントのコードは次のとおりです。 <div class="items no-v"> <FetchItemViewFunctional v-for="アイテムのアイテム" :key="アイテムID" :item="アイテム" @vote="投票アイテム(アイテム)" /> </div> 最適化されたコードは次のとおりです。 <リサイクルスクロール クラス="アイテム" :items="アイテム" :item-size="24" > <テンプレート v-slot="{ アイテム }"> <アイテムビューの取得 :item="アイテム" @vote="投票アイテム(アイテム)" /> </テンプレート> </リサイクル スクロール> 引き続き前の例を使用して、 最適化前: 最適化後: この 2 つの画像を比較すると、最適化されていない場合、10,000 データの fps は、スクロール時に 1 桁に過ぎず、スクロールしない場合は数十に過ぎないことがわかります。これは、最適化されていないシナリオではレンダリングされる DOM が多すぎるため、レンダリング自体に大きな負荷がかかるためです。最適化後、データ項目が 10,000 個あっても、スクロールする場合は fps が 30 を超え、スクロールしない場合は 60 フレームに達することができます。 この違いの理由は、仮想スクロールの実装方法にあります。仮想スクロールでは、ビューポート内の DOM のみがレンダリングされます。この方法では、レンダリングされる DOM の合計数は非常に少なくなり、パフォーマンスは当然大幅に向上します。 仮想スクロール コンポーネントも Guillaume Chau によって作成されました。興味のある学生は、そのソース コードの実装を学習できます。その基本原理は、スクロール イベントをリッスンし、表示する必要のある DOM 要素を動的に更新し、ビュー内でのそれらの変位を計算することです。 仮想スクロール コンポーネントは、スクロール プロセス中にリアルタイムで計算する必要があるため、コストがかからないわけではなく、一定の 要約するこの記事を通じて、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 をよろしくお願いいたします。 以下もご興味があるかもしれません:
|
<<: LINUX でポートが占有されているかどうかを確認する方法
目次1. 左端接頭辞原則2. 表に戻る3. インデックスプッシュダウン序文:インデックス プッシュダ...
ウェブサイトのさまざまな HTML ドキュメントはハイパーリンクを通じて相互に接続され、一貫性のある...
Crontab は定期的な実行を設定するために使用されるコマンドです。そのデーモン プロセスは cr...
序文クラスメートが MLSQL Stack のストリーミング サポートを調査しています。そこで、フロ...
効果: <!doctypehtml> <html> <ヘッド> ...
新しい設定ファイルを作成します (たとえば、nginx インストール ディレクトリの下の conf ...
<iframe src=”test.jsp” width=”100″ height=”50″ ...
目次1. はじめに2. データを消去するいくつかの方法2.1 ref() の使用2.2 スライスの使...
Vue でフォーム フィールドを記述および検証する方法は多数あります。このブログでは、より一般的に使...
この記事の例では、WeChatアプレットマップで使用される具体的な実装コードを参考までに共有していま...
Web デザインでよく耳にするプロパティ名: content、padding、border、marg...
1. 参照整合性参照整合性とは、主に外部キー制約を使用した複数のテーブル間の設計を指します。複数テ...
おそらく、この記事にこのようなタイトルを付けると、誰かがこう尋ねるでしょう。「なぜまだテーブルに注目...
1.位置:固定一部の Web サイトの右下隅にあるポップアップ ウィンドウなどの、ブラウザーを基準と...
この記事では、divのドラッグ可能な高さを実現するためのVueの具体的なコードを参考までに共有します...