[翻訳] Frontend performance pattern

# Frontend performance pattern

[原文](https://thanhle.blog/blog/frontend-performance-pattern-en)

## なぜこの記事を読むべきでしょうか？

* フロントエンドのパフォーマンス最適化に使用される一般的なパターン
* ウェブアプリの速度向上
* 上司や同僚の説得

パフォーマンスをいつ考慮すべきでしょうか？

私にとって、フロントエンドアプリは3つの側面を維持管理する必要があります。

* 機能的 - アプリは正しいロジックで実行される必要があります。
* 保守可能/読みやすい - アプリが正しく実行される場合、保守および新機能の追加が容易である必要があります。
* パフォーマンス - 高速でユーザーエクスペリエンスを楽しいものにする必要があります。

## いつパフォーマンスに関心を持つべきでしょうか？

まず、アプリのシステム、構造を設計するたびに、トレードオフをしているということに同意しましょう。システムを問題に合わせて作成するために、ある部分を切り捨て、別の部分を取得します。もし私たちがすべてを望むとしたらどうでしょうか？プロジェクト開始時にはリソースは常に限られているため、不可能です。しかし、その逆の問題は際限なく大きくなる可能性があります。

* Centralize vs Decentralize
* Monolithic vs Microservice
* SSR vs CSR
* OOP vs FP
* SQL vs No-SQL
* Language X vs Language Y
* API vs GraphQL
* Stream vs Batch

それでは、いつパフォーマンスについて考慮すべきでしょうか？そして、何が得と失の関係でしょうか？私にとって、フロントエンドアプリは3つの側面を維持する必要があります。

* 機能的 - アプリは正しいロジックで実行される必要があります。
* 保守可能/読みやすい - 正しく実行される場合、保守が容易で新しい機能を追加できる必要があります。
* パフォーマンス - 高速でユーザーエクスペリエンスを楽しいものにする必要があります。

機能性は理解しやすいです。これは私たちがトレードオフできない唯一の側面です。したがって、私たちのアプリケーションはきれいなコードとパフォーマンスの間のスライダーになります。これは各プロジェクトと問題によってトレードオフが異なります。

> プロジェクトに参加する開発者として、何がより重要かを知る権利があります。高速で多くの機能をリリースすることを望みますか？それとも、機能が非常に高速に実行されることを望みますか？

> 「私はかつてパフォーマンスに過度に執着しており、多くのジュニア開発者も私と同じような執着を持っていることに気づきました。例えば、かつてCSVファイルを検証するスクリプトを並列処理するのに一日がかかったことがあります。最適化の結果は良好に見え、実行時間が15分から4分に短縮されました。しかし残念ながら、このスクリプトは週に一度しか実行されず、結果的に無料サーバーで月に約40分しか節約できませんでした。」

## Split code/Lazyload

**難易度:** 簡単

**適用時期:** プロジェクト開始直後から、ページ/ルートごとにコードを分割するなどの簡単な方法から始めることができます。その後、さらに進めたい場合は、ユーザーの相互作用に応じてコードを分割できます。

**命令:** ユーザーに必要なものだけをロードします。

**方法:** フレームワークによって異なるため、Googleで次の公式で検索してください: `Framework + コード分割`

**Reactの例**

[React公式ドキュメント](https://reactjs.org/docs/code-splitting.html)を参照してください。

```jsx
import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}
```

## 重複したライブラリのインストール防止

**難易度:** 非常に簡単

**適用時期:** 新しいライブラリをインストールしようと検討するとき。次の3つのオプションがあります。

* 既存のライブラリを使用する場合、問題に合わせて使用することを願います。
* 新しいライブラリを使用する場合、レガシーコードを変更し、レガシーの問題に合わせて使用することを願います。回帰バグテストを実行します。
* 両方のライブラリをすべて使用します。⇒ これは最後の望みです...まるでPMが首にナイフを突きつけているかのように。

現在のプロジェクトでは、日付/時刻を処理するために`moment`、`date-fns`、および`dayjs`という3つのライブラリを使用しています。`moment`と`date-fns`はバンドルサイズが大きいです。[You might not need date fns](https://dev.to/dmtrkovalenko/you-might-not-need-date-fns-23f7)を参照してください。

**命令:** 新しいライブラリを検索/インストールする前にpackages.jsonを確認してください。

## ES6およびツリーシェイキングをサポートするライブラリを選択してください

**難易度:** 簡単、ただしコミュニティによって異なる

**適用時期:** 常に。バンドルサイズとツリーシェイキングのサポートは考慮すべき重要な事項です。

**命令:** 最新のライブラリほど優れている可能性が高いです。（ただし、安定していて正しいという保証はありません。）

**方法:** https://bundlephobia.com/ でライブラリをチェックしてください。

![redux bundle size is 1.6kB when Gzip and supported tree-shaking](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2F49437f05-1c11-48e2-ba7e-8e3936dcbced%2F1a062eb2-1ce7-481e-bfb4-9c30bc61e025%2FUntitled.png%3Ftable%3Dblock%26id%3Dc99a49c9-5f01-4a7b-9607-75833311e29e%26spaceId%3D49437f05-1c11-48e2-ba7e-8e3936dcbced%26expirationTimestamp%3D1737309600000%26signature%3D0Z11w7OAvB8CLXy9EEFNCwqKimuZqcANkPLlECS8ZIo?table=block&id=c99a49c9-5f01-4a7b-9607-75833311e29e&cache=v2)

[Minimizing bundle size](https://mui.com/guides/minimizing-bundle-size/)

## ユーザー入力のデバウンス

**難易度:** 非常に簡単

**適用時期:** ユーザー入力（タイピング、スクロールイベントなど）を処理するときにいくつかのタスクを実行する必要がある場合

**命令:** 検索入力 ⇒ デバウンス

**デバウンスとは？** (翻訳者が追加した内容)

ウェブ用プログラムでは、入力処理関数はフレームの完了をブロックし、不要なレイアウト作業を引き起こす可能性があるため、パフォーマンス問題の潜在的な原因となる可能性があります。長時間実行される入力処理関数はスクロールをブロックする可能性があるため、避ける必要があります。また、入力処理関数でスタイル変更を実行しないでください。

**デバウンスの利点:**

* パフォーマンス向上: ユーザー入力に対する操作を遅延させることで、ブラウザが他のタスク（例: 画面レンダリング、イベント処理）を実行できるようにします。
* 入力削減: 高速に入力するユーザーの場合、不要な操作を減らします。

**例:**

* 検索フィルター: ユーザーが検索窓に入力するたびにAPIを呼び出す代わりに、ユーザーが入力を完了した後、または一定時間が経過した後にのみAPIを呼び出すようにします。
* リアルタイムチャット: ユーザーがメッセージを入力するたびにサーバーにメッセージを送信する代わりに、ユーザーが入力を完了した後にのみメッセージを送信するようにします。

参考資料: [Debounce Your Input Handlers](https://developers.google.com/web/fundamentals/performance/rendering/debounce-your-input-handlers)

**より深い理解:** コンピュータの性能が低い場合、API応答にもデバウンスを使用できます。例えば、取引または注文帳に対する応答をデバウンスするのが一般的な場合です。

## タグ img、iframeにloading=lazyを追加

**難易度:** 簡単

**適用時期:** ほとんどの場合、imgタグを見るときに適用します。ただし、imgがビューポートの上部にある場合は適用する必要はありません。

**命令:** 画像 + loading=lazy ⇒ ✈️

![img tag with loading=lazy](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2F49437f05-1c11-48e2-ba7e-8e3936dcbced%2F1f917c6a-630c-4523-9282-575b61b7b277%2FUntitled.png%3Ftable%3Dblock%26id%3Da74d54f4-7d04-4d5c-800f-e92d44f76371%26spaceId%3D49437f05-1c11-48e2-ba7e-8e3936dcbced%26expirationTimestamp%3D1737309600000%26signature%3DaBKe4CsfoLvxYcWzyHxgQp4IJEUxXakEeUJdVeLUH3M?table=block&id=a74d54f4-7d04-4d5c-800f-e92d44f76371&cache=v2)

##メモイぜーション関数

**難易度:** 普通

**適用時期:** 関数の実行に多くのCPUとRAMリソースを消費する場合

**命令:** 高価な操作のキャッシュ

**メモイぜーションとは？** (翻訳者が追加した内容)

関数はプログラムで重要な部分です。コードのモジュール性と再利用性を高めます。プログラムを関数単位に分けて後で呼び出して有用な操作を実行するのが一般的です。しかし、関数を複数回呼び出す場合、コストがかかる場合があります。

**メモイぜーションを使用する理由:**

* **パフォーマンス向上:** 同一の入力値で関数を複数回呼び出す場合、計算結果をキャッシュして再利用することでパフォーマンスを向上させることができます。
* **効率性増加:** キャッシュを通じて関数実行時間を短縮し、プログラム全体の効率性を高めることができます。

**例:**

* 大きな数列のフィボナッチ数計算
* 複雑なデータ処理作業

**参考資料:**

* [Understanding Memoize in JavaScript](https://www.freecodecamp.org/news/understanding-memoize-in-javascript-51d07d19430e/) | freeCodeCamp.org

**追加的な方法:**

* ウェブワーカーを使用して計算作業をバックグラウンドプロセスに分離することもできます。

```jsx
const cachedResult = useMemo(() => {
  // CPU intensive task here
}, [dependencies]);
```

## フロントエンドパフォーマンス最適化パターン

### サービスワーカーを使用したフロントエンドアセットのキャッシュ

**難易度:** 普通、難しい（初めて使用するのは難しいが効果は非常に良い）

**適用時期:** 非常に大規模なウェブアプリを扱っており、バンドルサイズが大きい場合（複雑な管理者/CRMなど）

**命令:** 複雑で大きなウェブアプリ ⇒ サービスワーカー

**例（React）:**

React PWAの有効化設定 with Workbox

Reactを使用してPWAを作成するのは簡単です！しかし、実際に機能を追加したくない場合を除いては、です。この記事では、インストール可能性、事前キャッシュ、バックグラウンド同期、プッシュ通知など、実際のプログレッシブウェブアプリのすべての機能を備えたReactおよびWorkboxを使用してPWAを作成できるように案内します。

**参考資料:**

* [React PWA with Workbox](https://dev.to/noconsulate/react-pwa-with-workbox-6dl)

**利点:**

* ユーザーは最初のローディング画面だけを見て、残りのプロセスはオフライン状態でも利用できます。
* バックグラウンドでアプリを更新できます。

### 仮想リスト

**難易度:** 難しい

**適用時期:** 多くの項目を含むリストを扱っており、ユーザーがすべての項目を見るためにスクロールする必要がある場合

**命令:** 100個以上の項目があるテーブル、FacebookやTwitterフィードのようなリスト実装時 ⇒ 仮想リスト

**React仮想ライブラリ:**

React仮想ライブラリは開発者の便宜のために設計されています。仮想ライブラリは逆制御を使用し、開発者が実際のコントロールを維持できます。何かをカスタマイズしたい場合はその方法を提供します。しかし、必要なもの以上を要求しないでください！

**参考資料:**

* [React Virtual](https://react-virtual.tanstack.com/)

**推奨事項:**

仮想リストを使用する際は、開発者が仮想リストが動作する仕組みとコンポーネントが再レンダリングされる時点を理解する必要があり、最大限の効果を発揮できます。そうでない場合は、逆にパフォーマンスの低下を招く可能性があります。

![](https://www.notion.so/image/https%3A%2F%2Fmedia.giphy.com%2Fmedia%2FxvqIOHfQvth5NeoT0Y%2Fgiphy.gif?table=block&id=2178593c-eb1c-4645-85bc-8430cb5ddca8&cache=v2)

### 長時間実行機能を複数の短期実行機能に分割

**難易度:** 難しい

**適用時期:** 関数を実行したときにノートパソコンがフリーズした場合

**命令:** 上記と同様

**方法:** 長時間実行関数を`setTimeout`または`requestAnimationFrame`を使用して複数の短期実行関数に分割します。しかし、長時間実行関数を多くの小さな関数に分割するのは簡単な作業ではなく、時には関数が順番に実行されるように維持する必要があり、関数が常に正しい結果を返すように保証する必要があります。

### 楽観的アップデート

**難易度:** 簡単または普通または難しい

* 簡単なエンティティに適用する場合は簡単です。
* ローカル状態とサーバー状態間の衝突が発生する場合は普通の難易度です。
* ロジックが複雑でローカル状態とサーバー状態間の衝突を解決する必要がある場合は難しいです。
* 例：いいねボタンは簡単、コメントは普通、状態の投稿は非常に難しいです。

**適用時期:** 機能が非常に簡単でAPI成功率が約99.99％に近い場合

**命令:** 簡単なロジック、99.99％成功 ⇒ 楽観的アップデート

**楽観的UI構築による電光石火のフロントエンド**

ウェブアプリをより高速に構築するための絶え間ない努力の中で、どのようなオプションも除外することはできません。私たちは読み取りと書き込みを最適化するためにデータベースを分割し、サービスを需要に応じて拡張および縮小し、これらすべての上に複雑なキャッシュ戦略を使用します。

**参考資料:**

* **楽観的UI構築による電光石火のフロントエンド:** [https://derekndavis.com/posts/lightning-fast-front-end-build-optimistic-ui](https://derekndavis.com/posts/lightning-fast-front-end-build-optimistic-ui)

### 遅延ポリフィル/動的ポリフィル

**難易度:** 普通、難しい

**適用時期:** これ以上最適化する方法がない場合

**命令:** ポリフィルバンドルサイズが非常に大きいが、ユーザーがすべて最新技術を使用している場合

**方法:** 現在最も一般的に使用されている方法はPolyfill.ioです。ただし、フロントエンドとバックエンドの両方を設定する必要があるため、やや複雑です。

**どのようなパターンを使用してパフォーマンスを最適化していますか？一緒にさらに多くのことを学びたいです。**