【JS応用】どこに書きますか?

フロントエンド開発における「どこに書くか」という設計思想の極致

フロントエンド開発の現場において、多くのエンジニアが直面する最も普遍的かつ難解な問い、それが「どこに書くか」という配置問題です。コンポーネント指向が主流となった現代において、ロジックをコンポーネント内に置くのか、カスタムフックに逃がすのか、あるいはグローバルな状態管理ライブラリに預けるのか。この意思決定の積み重ねが、数年後のアプリケーションの保守性、拡張性、そしてパフォーマンスを決定づけます。

本稿では、コードの配置場所を決定するための指針を、単なるディレクトリ構成論を超えた「責務の分離」という観点から深掘りします。

コンポーネント内部:UIの宣言的表現の聖域

Reactをはじめとする現代のUIフレームワークにおいて、コンポーネントは「UIの最小単位」であると同時に「UIの振る舞いを記述する場所」です。しかし、ここにビジネスロジックやAPI通信、複雑なデータ変換処理が混在すると、いわゆる「肥大化したコンポーネント」が誕生します。

コンポーネント内部に記述すべきは、「UIの表示状態」と「それらに直結するイベントハンドラ」に限定すべきです。具体的には、`useState`や`useRef`を用いた一時的なUI状態、そしてpropsから受け取ったデータの描画処理です。

もし、コンポーネント内に「データの取得」や「複雑な計算」が入り込み始めたら、それは「どこに書くか」を再考すべきサインです。コンポーネントは、あくまで「何をどう見せるか」という宣言的な記述に専念させるべきであり、「どうデータを加工するか」という手順の記述からは解放される必要があります。

カスタムフック:ロジックの再利用性と疎結合化の鍵

ビジネスロジックをコンポーネントから引き剥がす際の第一選択肢が「カスタムフック」です。カスタムフックは、単なるロジックの切り出し場所ではなく、アプリケーションの「ドメイン知識」を抽象化するレイヤーとして機能します。

例えば、ユーザーの認証状態に応じたバリデーションロジックや、APIのレスポンスを特定の型に変換する処理をカスタムフックに分離することで、コンポーネントは純粋なUI層へと回帰します。

// 悪い例:コンポーネント内で直接APIを叩き、複雑な加工を行う
const UserProfile = () => {
  const [data, setData] = useState(null);
  useEffect(() => {
    fetch('/api/user').then(res => res.json()).then(d => {
      const formatted = { fullName: `${d.firstName} ${d.lastName}`, ... };
      setData(formatted);
    });
  }, []);
  return 
{data?.fullName}
; }; // 良い例:ロジックをカスタムフックに分離 const useUser = () => { const [user, setUser] = useState(null); useEffect(() => { // データ取得と加工の責務をここに集約 }, []); return user; };

カスタムフックに書くことで、テストも容易になります。UIの描画を伴わずにロジックのみを検証できるため、単体テストの効率が劇的に向上します。

ビジネスロジック層:コンポーネントから独立した純粋関数

さらに高度な設計を目指す場合、カスタムフックすらも「Reactのライフサイクル」に縛られているという制約に気づくはずです。真にポータブルなロジックは、フレームワークに依存しない「純粋関数(Pure Function)」として記述すべきです。

ディレクトリ構成として`src/utils`や`src/domain`を作成し、そこに純粋なデータ変換処理やバリデーションロジックを配置します。これらは、Reactコンポーネントからも、Vueのコンポーネントからも、あるいはNode.jsのバックエンドからも呼び出せる可能性を秘めています。

「どこに書くか」という問いに対する最高品質の回答は、「フレームワークに依存しない場所に書き、フレームワーク層からそれを呼び出す」という構造を作ることです。これにより、将来的なフレームワークの移行や、マルチプラットフォーム展開が現実的なコストで実現可能になります。

状態管理ライブラリ:グローバルとローカルの境界線

ReduxやZustand、Recoilといった状態管理ライブラリの配置場所を巡る議論も絶えません。ここで重要なのは、「その状態は本当にグローバルである必要があるか?」という自問自答です。

多くの開発者が、コンポーネント間でpropsバケツリレーが発生することを恐れ、安易にグローバルストアにデータを放り込みます。しかし、これは「カプセル化の破壊」を招きます。ある特定の機能単位でしか使われないデータがグローバルに存在すると、デバッグ時にそのデータの出所を追跡するのが極めて困難になります。

状態管理ライブラリは、あくまで「アプリケーション全体で共有されるべき共通状態(認証情報、テーマ設定、通知キューなど)」のために使用すべきです。それ以外の「機能的な状態」は、コンポーネントの近く、あるいはReact Contextを活用して、必要な範囲だけにスコープを限定するのが、保守性を高める秘訣です。

実務アドバイス:可読性と保守性を最大化する意思決定基準

実務において「どこに書くか」を迷った際、以下の基準をチェックリストとして活用してください。

1. 抽象度:そのコードは「UIの見た目」の話か、「ビジネスのルール」の話か。前者はコンポーネントへ、後者はフックまたは純粋関数へ。
2. 再利用性:他の画面でも同じロジックが必要か。必要であれば、即座に`hooks`または`utils`へ抽出する。
3. 依存関係:その処理はReactのhook(useEffectなど)を必要とするか。必要としないなら、純粋関数として独立させる。
4. 変更頻度:UIとロジックのどちらが変更されやすいか。変更頻度が異なるものは、ファイルを分けるのが原則です。

また、ディレクトリ構造を「機能単位(Feature-based)」にするか「役割単位(Layer-based)」にするかも重要です。小規模プロジェクトでは役割単位でも機能しますが、中規模以上では機能単位のディレクトリ構成を強く推奨します。`features/auth`, `features/dashboard`のように分けることで、関連するコンポーネント、フック、型定義が物理的に近くに配置され、コードの検索性と理解が向上します。

まとめ:設計とは「捨てる場所」の決定である

「どこに書くか」という問いの本質は、どこにコードを置くかというポジティブな選択よりも、どこにコードを置かないかというネガティブな選択にあります。コンポーネントにロジックを置かないという選択、グローバルストアに状態を置かないという選択。これらが、結果として疎結合なアーキテクチャを形作ります。

フロントエンド開発は、単なるWebページの作成から、複雑なアプリケーションの構築へと進化しました。その中で、コードの配置場所に関する規律を守り続けることは、エンジニアとしての技術的負債をコントロールする唯一の手段です。

今日書いたその数行のロジックが、半年後の自分を救うのか、それとも苦しめるのか。その分かれ道は、あなたが今、そのコードを「どこに置くか」という判断の中にあります。常に「責務の分離」を意識し、コンポーネントをUIの純粋な表現として保ち続けること。それこそが、プロフェッショナルなフロントエンドエンジニアが追求すべき、最高品質の設計思想です。

コメント

タイトルとURLをコピーしました