ReactでSlotMachineを作る(Step5:ディレクトリ整理/リファクタリング)

## はじめに
[前回](https://qiita.com/koji0705/items/bb1708c50978218607ec)までの記事の続きです。(シリーズ化予定)

▪️実装Step
[Step1:基本編](https://qiita.com/koji0705/items/be905a51d5b9674f4d46)
[Step2:Coin機能実装編](https://qiita.com/koji0705/items/7c4fe495252728e5457c)
[Step3: 仕様追加、リファクタリング実施](https://qiita.com/koji0705/items/bb1708c50978218607ec)
[Step4:ワイルドカード、セブンカードの追加](https://qiita.com/koji0705/items/037a0fd7ea93bf6c63f3)
[Step5:ディレクトリ整理/リファクタリング](https://qiita.com/koji0705/items/a8b12ea24b774129f784) ←いまここ


## 成果物
![step4_seven.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/d749d0a2-23d7-b3d2-19c6-de5f3feed96e.gif)

https://github.com/kouji0705/react_slot_machine/tree/feat/Step5_refact

▪️実施内容
・ディレクトリ構成の整理
・useReducerを活用したリファクタリング

## ソースコード
```shell:ディレクトリ構成
~/develop/react/react_slot_machine/src$ tree 
.
├── App.tsx
├── features
│   └── slotMachine
│       ├── components
│       │   ├── Dialog.tsx
│       │   ├── Reel.tsx
│       │   ├── index.test.tsx
│       │   └── index.tsx
│       ├── constants
│       │   ├── actions.ts
│       │   ├── slot.ts
│       │   └── symbols.ts
│       ├── types
│       │   ├── action.ts
│       │   ├── state.ts
│       │   └── symbol.ts
│       └── utils
│           ├── reducer.ts
│           ├── spinReels.test.ts
│           └── spinReels.ts
├── index.tsx
├── logo.svg
├── pages
│   └── index.tsx
└── setupTests.ts

8 directories, 18 files
```

### ディレクトリの整理
```shell:旧ディレクトリ構成
~/develop/react/react_slot_machine/src$ tree -d      
.
└── components

2 directories
```

```shell:新ディレクトリ構成
~/develop/react/react_slot_machine/src$ tree -d    
.
├── features
│   └── slotMachine
│       ├── components
│       ├── constants
│       ├── types
│       └── utils
└── pages

8 directories
```

### Point 1: 機能とページの切り分け

- **課題**
  - 前回までは機能中心のアプローチで開発を実施。
  - URL単位で管理するページ中心のアプローチを取り入れる必要がある。

- **解決策**
  - `src` 配下を **features** と **pages** で分離。
    - **features**: コンポーネント単位の実装を行う。
    - **pages**: 作成したコンポーネントを呼び出し、ページ単位での実装を行う。

### Point2: features配下のディレクトリ整理
```shell:src/features/slotMachine配下
~/develop/react/react_slot_machine/src/features/slotMachine$ tree
.
├── components
│   ├── Dialog.tsx
│   ├── Reel.tsx
│   ├── index.test.tsx
│   └── index.tsx
├── constants
│   ├── actions.ts
│   ├── slot.ts
│   └── symbols.ts
├── types
│   ├── action.ts
│   ├── state.ts
│   └── symbol.ts
└── utils
    ├── reducer.ts
    ├── spinReels.test.ts
    └── spinReels.ts

5 directories, 13 files
```

:::note info
components,constants,types,utilsを作成。
componentsはReactComponent。constantsやtypes,utilsの部品を呼び出して形成。
constantsは定数。定数の中でもイベントやオブジェクトに応じた定数化。
typesは型。型の中でもイベントやオブジェクトに応じた型定義。
utilsは関数。用途に応じて関数化を実施。
:::

### useReducerの活用

- **課題**
  - 関連する状態更新が複数の変数で管理されている。
  - アクションタイプに基づいた状態更新が行えていない

- **解決策**
  - useReducerを活用する。

```tsx:修正前
export const SlotMachine: React.FC = () => {
  const [coins, setCoins] = useState(INITIAL_COINS);
  const [reels, setReels] = useState<SymbolTile[]>(Array(3).fill(SYMBOLS[0])); // SymbolTile型の配列
  const [isJackpot, setIsJackpot] = useState(false); // ジャックポット状態の追加

  const handleSpin = () => {
    const {newReels, win, jackpot, points} = spinReels(SYMBOLS, BET_COINS);
    setReels(newReels);
    setIsJackpot(jackpot);
    setCoins((prev) => prev - BET_COINS + (win ? points : 0));
  };

  const isWin = new Set(reels.map((tile) => tile.id)).size === 1;

  return (
    <Box sx={{textAlign: "center", marginTop: 4}}>
      <Box>Coinの枚数：{coins}枚</Box>
      <Box sx={{display: "flex", justifyContent: "center", gap: 2}}>
        {reels.map((tile, index) => (
          <Reel key={index} symbol={tile.label} />
        ))}
      </Box>
      <Button
        variant="contained"
        color="primary"
        style={{marginTop: "16px"}}
        onClick={handleSpin}
        disabled={coins < BET_COINS}
      >
        スピン
      </Button>
      {isJackpot && <JackpotDialog />}
      {isWin && <Box sx={{marginTop: 2}}>勝利！</Box>}
      {isJackpot && <Box sx={{marginTop: 2}}>ジャックポット！</Box>}
    </Box>
  );
};
```

```tsx:修正後
export const SlotMachine: React.FC = () => {
  const [state, dispatch] = useReducer(slotMachineReducer, initialState);

  const handleSpin = () => {
    dispatch({type: SPIN_ACTION});
  };

  const handleCloseJackpot = () => {
    dispatch({type: CLOSE_JACKPOT_ACTION});
  };

  return (
    <RootContainer>
      <Box>Coinの枚数：{state.coins}枚</Box>
      <Reels>
        {state.reels.map((tile, index) => (
          <Reel key={index} symbol={tile.label} />
        ))}
      </Reels>
      <SpinButton
        variant="contained"
        color="primary"
        onClick={handleSpin}
        disabled={state.coins < BET_COINS}
      >
        スピン
      </SpinButton>
      {state.isJackpot && <JackpotDialog onClose={handleCloseJackpot} />}
      {state.isWin && <DisplayWin>勝利！</DisplayWin>}
    </RootContainer>
  );
};
```


**Point1:定義した変数を1つのオブジェクトに吸収**
修正前は3つ`coins`,`reels`,`isJackpot`の3つを定義。
バラバラで定義したため、更新元で色々変更する必要あった。
→useReducerを利用する事で、1つの`state`のオブジェクト内に包括された。
```tsx:修正後
  const [state, dispatch] = useReducer(slotMachineReducer, initialState);

  const handleSpin = () => {
    dispatch({type: SPIN_ACTION});
  };

  const handleCloseJackpot = () => {
    dispatch({type: CLOSE_JACKPOT_ACTION});
  };
```

**Point2:ビジネスロジックが外部の関数に移行**
onClickイベントでは、イベント内容のみを引数として渡した。
その結果、コンポーネント内ではビジネスロジックやstateの更新を一切意識する必要がなくなった。

```tsx:修正後のonClickイベント
  const handleSpin = () => {
    dispatch({type: SPIN_ACTION});
  };

  const handleCloseJackpot = () => {
    dispatch({type: CLOSE_JACKPOT_ACTION});
  };
```

## まとめ
ディレクトリ構成、リファクタリングを実施した事で、責務の分離が一層やりやすくなりました！！！
責務の分離は保守性・可読性の向上はもちろんのこと、テストも実施しやすくなるため、是非試してみてください！！！！