2025/07/23

テクノロジー

【マイナビジョブサーチ】Next.js(Pages Router)を用いた事例紹介

この記事の目次

    はじめに

    マイナビジョブサーチのフロントエンド開発において、コードの可読性・保守性向上を目的としたリファクタリングを実施しました。本記事では、実際に行ったリファクタリング内容とその背景についてまとめています。

    コンテナ・プレゼンテーションパターンを採用

    これまでのコンポーネントは、UIとビジネスロジックが1つのコンポーネントに混在しており、1つのファイルのソースコード量が膨大であり可読性が悪かったです。その他にも、UIとビジネスロジックが同じファイルにあったため、UIとビジネスロジックをそれぞれ単体でテストすることが難しかったです。

    そこで、メンテナンス性や拡張性を向上させるために、コンテナ・プレゼンテーションパターンを導入し、UIとビジネスロジックを明確に分離しました。

    • UI部分
      • Reactのコンポーネントで実装
    • ビジネスロジック部分
      • Reactのhooksや純粋関数で実装
    /** Before */
    export const Component = () => {
        const useHooks1 = () => {
            // 
        }
        const useHooks2 = () => {
            // 
        }
        const logic = () => {
            // 
        }
    
        return (
            <div>
                <h1>Component</h1>
                <p>Some content here...</p>
                <p>{ logic() }</p>
            </div>
        )
    }
    
    ⬇️⬇️⬇️
    
    /** After */
    import { useHooks } from "./hooks/useHooks"
    import { logic } from "./services/someLogic"
    
    export const Component = () => {
    const { ... } = useHooks()
    
        return (
            <div>
                <h1>Component</h1>
                <p>Some content here...</p>
                <p>{ logic() }</p>
            </div>
        )
    }
    

    このアプローチにより、1つのファイルあたりのコード量が減少し、可読性が高まりました。また、UIとビジネスロジックが明確に分離されたことで、各部分を独立してテストすることが容易になりました。

    ディレクトリ構成の見直し

    ディレクトリ構成の見直しにより、各ディレクトリのルールが明確になり、ファイルの配置が整理されました。また、命名規則も統一することで、プロジェクト全体の可読性と一貫性を持たせるようにしました。

    1. src/styles
      • Before
        • stylesディレクトリにグローバルで利用するスタイルのファイル(reset, variable, ...)と、一部コンポーネントのみでしか利用されないスタイルのファイルが混在していた
      • After
        • グローバルで利用するスタイルのファイルのみをこのディレクトリに置く
        • 一部コンポーネントのみでしか利用されていなかったスタイルのファイルは、利用するコンポーネントフォルダに移動
    /** Before */
    styles/
        ├─ ComponentA
        ├─ ComponentB
        ├─ PageA
        ├─ PageB
        ├─ _variables.module.scss
        ├─ reset.scss
        ├─ ...
    
    ⬇️⬇️⬇️
    
    /** After */
    styles/
        ├─ _variables.module.scss
        ├─ reset.scss
        ├─ ...
    
    1. src/components
      • Before
        • 特定のファイルでしか利用されないコンポーネント、共通コンポーネント(ボタン、モーダルなど)として色々なファイルで使われるコンポーネントが混在していた
      • After
        • 共通コンポーネント(ボタン、モーダルなど)として色々なファイルで使われるコンポーネントのみをこのディレクトリに置く
    /** Before */
    components/
        ├─ Button/
        ├─ Modal/
        ├─ Recruit/
        ├─ JobDetail/
        
    
    ⬇️⬇️⬇️
    
    /** After */
    components/
        ├─ Button/
        ├─ Modal/
        
    
    1. src/features
      今回のリファクタリングで新しく作成したディレクトリであり、Reactのアーキテクチャの1つであるBulletproof-reactを参考にして取り入れました。
      • 特定のファイルでしか利用されないコンポーネントをこのディレクトリに置くことで、共通コンポーネントと特定のファイルでしか使われないコンポーネントの棲み分けをした
      • 機能(LayoutTop, JobDetail)フォルダ内に関連するコンポーネントを作成し、機能単位で管理する
    features/
        ├─ LayoutTop/
                ├─ Navigation/
                    ├─ index.tsx
                ├─ Navigation2/
                    ├─ index.tsx
                ├─ ...
        ├─ JobDetail/
                ├─ JobDetail1/
                    ├─ index.tsx
                ├─ JobDetail2/
                    ├─ index.tsx
        ├─ ...
    
    1. src/apps
      今回のリファクタリングで新しく作成したディレクトリ
    • Before
      • Pages Routerのpagesディレクトリにはtsxファイル(jsxファイル)以外は置けないため、スタイルのファイルやビジネスロジックのファイルが色々なディレクトリに置かれていた
    • After
      • src/appsディレクトリのコンポーネントはプレゼンテーションコンポーネントとして利用し、pagesディレクトリのファイルはコンテナコンポーネントとしてデータをpropsを通じて渡す
      • pagesディレクトリに置けなかったスタイルのファイルやビジネスロジックのファイルを、コンポーネントとして必要なファイルをまとめて配置
    /** Before */
    pages/
        ├─ search.tsx
        
    styles/
        ├─ search.modules.scss
        
    hooks/
        ├─ useSearchHooks.ts
    
    ⬇️⬇️⬇️
    
    /** After */
    apps/
        ├─ Search/
                ├─ hooks/
                        └─ **
                ├─ services/
                        └─ **
                ├─ index.tsx
                ├─ styles.modules.scss
    
    pages/
        ├─ search.tsx
    
    // pages/search.tsx
    
    import { Search } from "apps/Search"
    
    const Page: NextPage<Props> = ({
      props1,
      props2,
      props3
    }) => {
      return (
        <Search
          props1={props1}
          props2={props2}
          props3={props3}
        />
      );
    };
    
    export default Page;
    

    コンポーネントディレクトリの構成

    コンポーネントのディレクトリ構成やファイルの命名がコンポーネントによってバラバラだったため、ファイルの配置や命名規則を統一しました。

    • Before
      • 特定のコンポーネントのみでしか利用されない スタイルのファイルやビジネスロジックのファイルがコンポーネントディレクトリとは別ディレクトリに散らばっていた
      • コンポーネントのフォルダに入っていたり入っていなかったりとバラバラ
      • コンポーネントのファイル名とスタイルのファイル名がコンポーネント名になっていた
    • After
      - コンポーネントフォルダをキャメルケースで命名し、その中にファイルを格納
      - コンポーネントのファイル名を「index.tsx」、スタイルのファイル名を「styles.modules.scss」に統一
      - UIとビジネスロジックを分離したため、ビジネスロジックを置くフォルダを新しく作成
      - カスタムフックは「hooks」フォルダに置く
      - 純粋関数は「services」フォルダに置く
    /** Before */
    styles/
        ├─ ComponentA.modules.scss
        
    components/
        ├─ ComponentA.tsx
        ├─ ComponentB/
                ├─ ComponentB.tsx
                └─ ComponentB.modules.scss
    
    ⬇️⬇️⬇️
    
    /** After */
    components/
        ├─ ComponentA/
                ├─ hooks/
                    └─ **
                ├─ services/
                    └─ **
                ├─ index.tsx
                └─ styles.modules.scss
        ├─ ComponentB/
                ├─ index.tsx
                └─ styles.modules.scss
                
    

    コンポーネントファイル(index.tsx)のルール

    • コンポーネントの型指定
      • コンポーネントのPropsの型指定は、React.FCを使用し、Propsの名前を利用する場合はコンポーネント内でのみ使用する
      • コンポーネントを定義する際はReact.FCを使用し、React.VFCは使用しないこと
    • Propsの取得方法
      • Propsは分割代入を用いて取得する
    const Component: React.FC<Props> = ({ prop1, prop2, prop3 }) => {
      // 
    }
    
    • コンポーネントのエクスポート方法
      • コンポーネントのエクスポートは、default exportではなく、named exportを使用する
    export const Component: React.FC<Props> = ({ prop1, prop2, prop3 }) => {
      // 
    }
    
    • コードまとめ
      • 以下は、上記のルールに従ったコンポーネントの例
    export const Component: React.FC<Props> = ({ prop1, prop2, prop3 }) => {
      return (
        <div>
          <p>{prop1}</p>
          <p>{prop2}</p>
          <p>{prop3 ? 'True' : 'False'}</p>
        </div>
      );
    }
    

    コンポーネントのビジネスロジックディレクトリ(hooks, services)の構成

    ビジネスロジックディレクトリ(hooks, services)は、今回のリファクタリングで新しく作成したビジネスロジックを管理するためのディレクトリです。このディレクトリでは、ビジネスロジックを整理し、再利用性を高めることを目的としています。

    hooksディレクトリ

    このディレクトリは、カスタムフックを管理する

    useHooks.tsとuse**.tsの分割

    • useHooks.ts
      • 複数のカスタムフック(use**.ts)のビジネスロジックをまとめて管理し、将来的にビジネスロジックが増えることを考慮し、拡張性を持たせている
      • インポートしたカスタムフックは、スプレッド構文を用いて返すことで、各フックのプロパティを一つのオブジェクトとしてまとめて利用している
    import { use**1 } from "./hooks/use**1"
    import { use**2 } from "./hooks/use**2"
    
    export const useHooks = (({param1, param2, param3}: Params or **Params)) => {
      return {
        ...use**1(),
        ...use**2(),
      };
    };
    
    • use**.ts
      • use**.ts各カスタムフックは、ビジネスロジックが干渉しないもの同士で切り分けることによって、関連しているビジネスロジックが明確になる
    export const use** = (({param1, param2, param3}: Params or **Params)) => {
      //
    };
    
    • 引数の型指定
      • 引数の型指定を行う際には、Propsという名前を避け、Paramsや**Paramsなどの名前を使用する
    // useHooks.ts
    export const useHooks = (({param1, param2, param3}: Params or **Params)) => {}
    
    // use*.ts
    export const use** = (({param1, param2, param3}: Params or **Params)) => {}
    
    • 利用方法
      • useHooks.tsを、コンポーネントやページでインポートして利用する
        ├─ Component/
                ├─ hooks/
                    └─ useLoading.ts
                    └─ useRelaod.ts
                    └─ useCalc.ts
                    └─ useHooks.ts
                ├─ index.tsx
    
    import { useHooks } from "./hooks/useHooks"
    
    export const Component = () => {
        const { ..., ..., ...} = useHooks()
        
        return (
            // 
            // 
            // 
        )
    }
    

    useHooks.tsとuse**.tsに分割することで、各ファイルのコード量を削減できるだけでなく、ファイルごとにビジネスロジックが明確になるため、可読性と保守性が向上するようになりました。

    servicesディレクトリ

    このディレクトリは、Reactの機能を利用しない純粋関数のビジネスロジックを管理する

    • 利用方法
      • servicesディレクトリに作成した純粋関数のビジネスロジックをコンポーネントやページでインポートして利用する。
      • hooksディレクトリとは違い、1つのビジネスロジックのファイルにまとめて管理はしない
        ├─ Component/
                ├─ services/
                    └─ calcUtils.ts
                ├─ index.tsx
                
    
    import { CalcUtils } from "./services/calcUtils.ts"
    
    export const Component = () => {
        return (
            // 
            // 
        )
    }
    

    カスタムフックと純粋関数のビジネスロジックを分けることで、再利用性、テストの容易さ、依存関係が管理しやすくなりました。

    まとめ

    本記事では、マイナビジョブサーチのフロントエンド開発におけるリファクタリングの取り組みについて紹介しました。主なポイントは以下の通りです。

    1. UIとビジネスロジックの分離
      • UIとビジネスロジックを1つのファイルから分離することで、コードの可読性が向上しました。これにより、各部分の役割が明確になり、理解しやすくなりました。
      • ビジネスロジックはカスタムフックや純粋関数として管理され、UI部分はReactコンポーネントとして実装されることで、各部分のテストが容易になりました。
    2. コンテナ・プレゼンテーションパターンの導入
      • コンテナ・プレゼンテーションパターンを採用することで、UIとビジネスロジックを明確に分離し、メンテナンス性や拡張性を向上させました。このアプローチにより、コードの構造が整理され、各コンポーネントの役割が明確になりました。
    3. ディレクトリ構成の見直し
      • ディレクトリ構成を見直し、ファイルの配置や命名規則を統一することで、プロジェクト全体の可読性と一貫性が向上しました。特に、共通コンポーネントと特定のファイルでしか使われないコンポーネントの棲み分けができたことで、管理が容易になりました。
    4. 再利用性とテストの容易さ
      • カスタムフックと純粋関数のビジネスロジックを分けることで、再利用性が高まり、テストの容易さが向上しました。特に、純粋関数は副作用がないため、単体テストが簡単に行えるようになりました。
    5. 管理の効率化
      • バラバラに置かれていたファイルをコンポーネントとしてまとめて配置できるようになり、開発者が必要なファイルを見つけやすくなりました。

    このリファクタリングを通じて、コードの可読性、保守性、テストのしやすさが向上しました。
    もし参考になる内容がございましたら、ぜひご活用いただければと思います。

    ※本記事は2025年07月時点の情報です。

    著者:マイナビエンジニアブログ編集部