React アプリケーションの進化

Feb 09 2023
React アプリケーションを入れていますが、React アプリケーションというものはありません。つまり、JavaScript または TypeScript で書かれたフロントエンド アプリケーションがたまたま React をビューとして使用しているということです。

React アプリケーションを入れていますが、React アプリケーションというものはありません。つまり、JavaScript または TypeScript で書かれたフロントエンド アプリケーションがたまたま React をビューとして使用しているということです。ただし、Java EE アプリケーションを JSP アプリケーションと呼ばないのと同じように、これらを React アプリケーションと呼ぶのはフェアではないと思います。

多くの場合、アプリケーションを機能させるために、さまざまなものを React コンポーネントまたはフックに詰め込みます。アプリケーションが小規模であるか、ほとんどビジネス ロジックが含まれていない場合には、この種のあまり組織化されていない構造は問題になりません。しかし、多くの場合、ビジネス ロジックがフロントエンドに移行するにつれて、このすべてがコンポーネントに含まれていると問題が発生します。より具体的に言うと、このような種類のコードを理解するのは比較的手間がかかるだけでなく、コード変更のリスクも高まります。

この記事では、React をビューとして使用する場合にのみ、「React アプリケーション」を通常のアプリケーションに再構築するために使用できるいくつかのパターンとテクニックについて説明したいと思います (これらのビューを別のビュー ライブラリに交換することもできます)。尽力)。

ここで重要な点は、コードの各部分がアプリケーション内でどのような役割を果たしているかを分析する必要があるということです (表面的には同じファイルにパックされている可能性もあります)。ビューとビューなしのロジックを分離し、ビューなしのロジックを責任に応じてさらに分割し、適切な場所に配置します。

この分離により、表面的なビューをあまり気にすることなく、基礎となるドメイン ロジックを変更できます。またその逆も可能です。また、ドメイン ロジックは他の部分に結合されていないため、他の場所でのドメイン ロジックの再利用性が向上します。

React はビューを構築するための質素なライブラリです

React は本質的に、ユーザー インターフェイスの構築を支援するライブラリ (フレームワークではない) であることを忘れがちです。

ユーザー インターフェイスを構築するための JavaScript ライブラリ
— React ホームページ

かなり単純に聞こえるかもしれません。しかし、私は人々がデータを取得して、データが使用される場所でロジックを再構成する記述をしているケースを多く見てきました。たとえば、React コンポーネント内のuseEffectレンダリングのすぐ上のブロックでデータを取得したり、サーバー側から応答を受け取った後にデータのマッピング/変換を実行したりします。

// ...
​
useEffect(() => {
  fetch("https://address.service/api")
    .then((res) => res.json())
    .then((data) => {
      const addresses = data.map((item) => ({
        street: item.streetName,
        address: item.streetAddress,
        postcode: item.postCode,
      }));
​
      setAddresses(addresses);
    });
}, []);
​
// the actual rendering...

現実世界の React アプリケーションへようこそ

ほとんどの開発者は、React のシンプルさと、ユーザー インターフェイスをデータを DOM にマッピングする純粋な関数として表現できるという考えに感銘を受けました。そして、ある程度まではそうです。

しかし、開発者は、ネットワーク リクエストをバックエンドに送信したり、ページ ナビゲーションを実行したりする必要がある場合に苦労します。これらの副作用により、コンポーネントの「純粋さ」が低下するためです。これらのさまざまな状態 (グローバル状態またはローカル状態) を考慮すると、事態はすぐに複雑になり、ユーザー インターフェイスの暗い側面が現れます。

ユーザーインターフェースとは別に

React 自体は、計算やビジネス ロジックをどこに配置するかについてはあまり気にしません。これは、ユーザー インターフェイスを構築するためのライブラリにすぎないため、これは公平です。そして、そのビュー層を超えて、フロントエンド アプリケーションには他の部分もあります。アプリケーションを動作させるには、ルーター、ローカル ストレージ、さまざまなレベルのキャッシュ、ネットワーク リクエスト、サードパーティ統合、サードパーティ ログイン、セキュリティ、ロギング、パフォーマンス チューニングなどが必要になります。

このような追加のコンテキストをすべてReact コンポーネントまたはフックに詰め込もうとするのは、一般的に良い考えではありません。その理由は、一般に概念を 1 か所で混在させるとさらに混乱が生じるためです。まず、コンポーネントは注文ステータスのネットワーク要求を設定し、次に文字列から先頭のスペースを削除して別の場所に移動するロジックを作成します。読者は常にロジック フローをリセットし、さまざまな詳細レベルを行ったり来たりする必要があります。

すべてのコードをコンポーネントにパックすると、Todo や 1 つのフォーム アプリケーションなどの小規模なアプリケーションで機能する可能性があります。それでも、そのようなアプリケーションが一定のレベルに達すると、それを理解するための努力は重要になります。新機能の追加や既存の欠陥の修正は言うまでもありません。

さまざまな関心事を構造のあるファイルまたはフォルダーに分離できれば、アプリケーションを理解するために必要な精神的負荷は大幅に軽減されるでしょう。そして、一度に集中する必要があるのは 1 つのことだけです。幸いなことに、ウェブ以前の時代に遡って、十分に証明されたパターンがすでにいくつかあります。これらの設計原則とパターンは、デスクトップ GUI アプリケーションのコンテキストにおいて、一般的なユーザー インターフェイスの問題を解決するために調査され、十分に議論されています。

この記事では、Martin Fowler がビュー、モデル、データの階層化の概念をわかりやすくまとめています。

全体として、これは多くのアプリケーションにとって効果的なモジュール化形式であることがわかり、私は定期的に使用し、推奨しています。最大の利点は、3 つのトピック (ビュー、モデル、データ) を比較的独立して考えることができるため、集中力を高めることができることです。

大規模な GUI アプリケーションの課題に対処するために、階層化されたアーキテクチャが使用されてきました。確かに、これらの確立されたフロントエンド組織のパターンを「React アプリケーション」で使用できます。

React アプリケーションの進化

小規模なプロジェクトや 1 回限りのプロジェクトの場合、すべてのロジックが React コンポーネント内に記述されているだけである場合があります。合計で 1 つのコンポーネントまたは少数のコンポーネントのみが表示される場合があります。コードは HTML のように見えますが、ページを「動的」にするために使用される変数または状態のみが含まれています。useEffectコンポーネントのレンダリング後にデータをフェッチするリクエストを送信する場合もあります。

アプリケーションが成長するにつれて、より多くのコードがコードベースに追加されます。これらを整理する適切な方法がなければ、すぐにコードベースは保守不可能な状態になってしまいます。つまり、開発者がコードを読むのにより多くの時間を必要とするため、小さな機能を追加するだけでも時間がかかる可能性があります。

そこで、保守可能な問題を軽減するためのいくつかの手順をリストします。通常、もう少し手間がかかりますが、アプリケーションに構造を組み込むことは有益です。拡張可能なフロントエンド アプリケーションを構築するためのこれらの手順を簡単に確認してみましょう。

単一コンポーネントのアプリケーション

これはほぼ単一コンポーネント アプリケーションと呼ぶことができます。

図 1: 単一コンポーネントのアプリケーション

しかしすぐに、1 つのコンポーネントで何が起こっているかを読み取るだけでも多くの時間がかかることに気付きます。たとえば、リストを反復処理して各項目を生成するロジックがあります。configurationまた、他のロジックとは別に、わずかなコードでサードパーティ コンポーネントを使用するためのロジックもいくつかあります。

複数のコンポーネントのアプリケーション

コンポーネントを複数のコンポーネントに分割することにしました。結果の HTML で何が起こっているかをこれらの構造に反映させることは良いアイデアであり、一度に 1 つのコンポーネントに集中するのに役立ちます。

図 2: 複数のコンポーネントのアプリケーション

アプリケーションが成長するにつれて、ビューとは別に、ネットワーク リクエストの送信、ビューが使用できるようにデータをさまざまな形式に変換、サーバーに送り返すデータの収集などが必要になります。また、コンポーネントは実際にはユーザー インターフェイスに関するものではないため、コンポーネント内にこのコードを含めるのは適切とは思えません。また、一部のコンポーネントの内部状態が多すぎます。

フックによる状態管理

このロジックを別の場所に分割することをお勧めします。幸いなことに、React では独自のフックを定義できます。stateこれは、これらと状態が変化するときのロジックを共有するための優れた方法です。

図 3: フックによる状態管理

すごいですね!単一コンポーネントのアプリケーションから抽出された多数の要素があり、いくつかの純粋なプレゼンテーションコンポーネントと、他のコンポーネントをステートフルにする再利用可能なフックがいくつかあります。唯一の問題は、フックでは、副作用と状態管理とは別に、一部のロジックが管理に属しておらず、state純粋な計算に属しているように見えることです。

ビジネスモデルが登場

このロジックをさらに別の場所に抽出すると、多くの利点が得られることに気づき始めました。たとえば、このように分割すると、ロジックに一貫性があり、どのビューからも独立したものになります。次に、いくつかのドメイン オブジェクトを抽出します。

これらの単純なオブジェクトは、データ マッピング (ある形式から別の形式へ) を処理し、null をチェックし、必要に応じてフォールバック値を使用できます。また、これらのドメイン オブジェクトの数が増えると、物事をさらにきれいにするために、継承またはポリモーフィズムが必要になります。したがって、他の場所で役立つとわかった多くの設計パターンをここのフロントエンド アプリケーションに適用しました。

図 4: ビジネス モデル

階層化されたフロントエンド アプリケーション

アプリケーションは進化し​​続けますが、いくつかのパターンが出現することがわかります。一連のオブジェクトはどのユーザー インターフェイスにも属さず、基になるデータがリモート サービス、ローカル ストレージ、またはキャッシュからのものであるかどうかは関係ありません。そして、それらを異なるレイヤーに分割したいとします。

図 5: 階層化されたフロントエンド アプリケーション

上記の進化プロセスは高レベルの概要であり、コードをどのように構造化するか、少なくとも方向性はどうあるべきかについて感覚を掴む必要があります。ただし、理論をアプリケーションに適用する前に、考慮する必要がある詳細が数多くあります。

これは、 MartinFowler.comで私が公開した長い記事の抜粋です(この記事に素晴らしい場所を与えてくれた Martin に感謝します。何か問題を見つけた場合、それは私の責任であることに注意してください)。記事全体が長すぎて中規模のブログに収まりません。あまり多くの部分に分けたくありませんので、熟慮したリファクタリングの過程で概念を学びたい場合は読んでください。

このブログ投稿が有益でお役に立てば幸いです。このテーマについてさらに詳しく知りたい場合は、この投稿で説明したすべてのトピックなどをカバーするオンライン コースを作成しました。このコースはインタラクティブで魅力的なものになるように設計されており、実践的なヒントとテクニックが満載です。

読んで気に入ったら、私のメーリングリストに登録してください。私はブログ、書籍、ビデオを通じてクリーン コードとリファクタリングのテクニックを毎週共有しています。