Reactのアーキテクチャの変遷

# 背景
toB向けのプロダクトをReactで作っています。
2022年1月に入社してから2月にローンチし、今日まで改修を続けています。
本記事は、アプリケーションが大きくなるにつれて、ディレクトリ構造やコード分割をどのように変えていったかの備忘録になります。

## 初期（2022年2月）
MVPにむかって最短で走った
```
src/
├── App.tsx
├── components // Atoms, Molecules単位の要素をアプリで統一
├── constants  // 定数定義
├── contexts   // Context
├── index.tsx
├── organisms  // organismsの定義
├── pages
│   ├── Login.tsx
│   ├── Order.tsx
│   └── OrderList.tsx
├── providers  // Provider
└── stories    // storybook
```
```mermaid
flowchart TB

App --> Context.Provider -- Routing --> Pages

subgraph Pages
  PublicPage
  subgraph PrivateRoute
    Page
  end
end
```

## 資金調達間近（2022年6月）
ここが使いづらいとか、ニッチなエラーとか、メールで報告くださるユーザさんには感謝しかない
```
src/
├── App.tsx
├── components
├── constants
├── contexts
├── index.tsx
├── pages
│   ├── Contract
│   │   ├── ContractItem.tsx // organismsディレクトリが消え、organisms, templatesは各pageの下に置くことに 
│   │   ├── contractUtils.ts // なぜかutilがこんなところにある…
│   │   └── index.tsx
│   ├── Login
│   │   └── index.tsx
│   ├── OrderCreate
│   │   ├── OrderFormComplete.tsx
│   │   ├── OrderFormConditions.tsx
│   │   ├── OrderFormConfirm.tsx
│   │   └── index.tsx
│   ├── OrderDetail
│   │   ├── EstimateItem.tsx
│   │   ├── OrderEdit.tsx
│   │   ├── OrderRequestsContent.tsx
│   │   └── index.tsx
│   └── OrderList
│       ├── OrderCard.tsx
│       ├── index.tsx
│       └── useOrderList.tsx // customHookの片鱗が見える
├── providers
├── stories
├── tests // ようやくテストコードを追加
└── utils // いろんなところで使う便利関数を定義
```
このあたりでページを構成するファイルが長くなってきたので、Organismsの単位で別ファイルに分離し、見通しやすくした
```mermaid
flowchart TB

App --> Context.Provider -- Routing --> Pages

subgraph Pages
  PublicPage
  subgraph PrivateRoute
　　　　　　　　subgraph Page
      index --> Organisms
    end
  end
end
```

## 現在進行系（2022年10月~）
デザインリニューアルも兼ねてアーキテクチャ見直し
```
src/
├── @types
├── App.tsx
├── components
├── constants
├── contexts
├── hooks      // customHookがディレクトリとして独立
├── images     // 画像
├── index.tsx
├── pages
│   ├── Billing
│   ├── Contract
│   ├── Login
│   ├── OrderCreate                        // 他ページの中は省略
│   │   ├── OrderCreateContainer.tsx       // Container - Presenterモデルを採用し、よりtestableを目指す
│   │   ├── desktop                        // for PC
│   │   │   ├── OrderCreate.tsx            // Presenter
│   │   │   ├── OrderFormComplete.tsx      // ステップ3のorganisms
│   │   │   ├── OrderFormConfirm.tsx       // ステップ2のorganisms
│   │   │   └── OrderFormInput.tsx         // ステップ1のorganisms
│   │   ├── smartphone
│   │   │   └── 省略
│   │   └── tablet
│   │       └── 省略
│   ├── OrderDetail
│   ├── OrderList
│   ├── OrderUpdate
│   ├── Usage
│   └── UsageDetail
├── providers
├── stories
├── tests
└── utils
```
Container - Presenterモデルを採用した。
* Containerは、useStateやuseCustomHookを使い、状態と関数を保持する
* Presenterは、Containerから状態と関数を受け取り、ハンドラを登録し、UIを表示する

Presenterがstorybookに登録しやすくなり、ContainerをMock化することでテストも書きやすくなる。
```mermaid
flowchart TB

App --> Context.Provider -- Routing --> Pages

subgraph Pages
  PublicPage
  subgraph PrivateRoute
　　　　　　　　subgraph Page
      Container -- state, function --> Presenter
    end
  end
end
```
あとレスポンシブ対応をやめて、PC, SP, TableそれぞれのPresenterを用意し、react-device-detectを用いて分岐させた。
dynamic importを使うことで、3つのPresenterにそれぞれ別名をつけなくて済んだし、そもそもユーザのデバイスと違うPresenterをimportしなくていいので気に入っている。
```tsx
// この分岐毎回書くのだるいよね…どうにかApp側でできないかな〜
const Presenter = React.lazy(() =>
  isBrowser
    ? import("pages/Page/desktop/Presenter")
    : isTablet
    ? import("pages/Page/tablet/Presenter")
    : import("pages/Page/smartphone/Presenter")
);

const Container = () => {
  const [state, setState] = useState();
  const { submit } = useCustomHook();
  return (
    <Suspense fallback={<Loading />}>
      <Presenter
        state={state}
        setState={setState}
        submit={submit}
      />
    </Suspense>
  );
};
```

## インターン生から提案を受けて以下の変更を試みる予定
styled-componentを用いたJSXの定義をStyleファイルに分離する
```
src/
├── @types
├── App.tsx
├── components
├── constants
├── contexts
├── hooks
├── images
├── index.tsx
├── pages
│   ├── Billing
│   ├── Contract
│   ├── Login
│   ├── OrderCreate
│   │   ├── OrderCreateContainer.tsx       // Container
│   │   ├── desktop                        // for PC
│   │   │   ├── OrderCreatePresenter.tsx   // Presenter
│   │   │   └── OrderCreateStyle.tsx       // Preseterで使うJSXの定義
│   │   ├── smartphone
│   │   │   └── 省略
│   │   └── tablet
│   │       └── 省略
│   ├── OrderDetail
│   ├── OrderList
│   ├── OrderUpdate
│   ├── Usage
│   └── UsageDetail
├── providers
├── stories
├── tests
└── utils
```
JSXの定義を別ファイルに分けることで、マークアップの構成とスタイルの関心を分離できる。
たしかに1ファイルにしているとマークアップとスタイルを行き来して煩わしかったので良さそう。
```tsx
// XxxStyle.tsx
import styled from "styled-components";

export const Body = styled.div`
  width: 920px;
  height: 600px;
  display: flex;
  flex-direction: column;
  gap: 12px;
`;
export const Title = styled.h1`
  font-size: 28px;
`;

// XxxPresenter.tsx
import Button from "src/components/Button";
import { Body, Title } from "src/pages/xxx/desktop/XxxStyle";
const XxxPresenter = ({
  state,
  submit,
}: {
  state: string;
  submig: () => Promise<void> | void;
}) => {
  return (
    <Body>
      <Title>{state}</Title>
      <Button
        label={state}
        onClick={submit}
      />
    </Body>
  )
};
export default XxxPresenter;

// XxxContainer.tsx
const Presenter = React.lazy(() =>
  isBrowser
    ? import("pages/Page/desktop/XxxPresenter")
    : isTablet
    ? import("pages/Page/tablet/XxxPresenter")
    : import("pages/Page/smartphone/XxxPresenter")
);
const XxxContainer = () => {
  const [state, setState] = useState();
  const { submit } = useCustomHook();
  return (
    <Suspense fallback={<Loading />}>
      <Presenter
        state={state}
        submit={submit}
      />
    </Suspense>
  );
};
export default Container;
```