3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

記事投稿キャンペーン 「2024年!初アウトプットをしよう」

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

Last updated at Posted at 2024-01-02

はじめに

前回までの記事の続きです。(シリーズ化予定)

▪️実装Step
Step1:基本編
Step2:Coin機能実装編
Step3: 仕様追加、リファクタリング実施
Step4:ワイルドカード、セブンカードの追加
Step5:ディレクトリ整理/リファクタリング ←いまここ

成果物

step4_seven.gif

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

ソースコード

ディレクトリ構成
~/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

ディレクトリの整理

旧ディレクトリ構成
~/develop/react/react_slot_machine/src$ tree -d      
.
└── components

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

8 directories

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

  • 課題

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

    • src 配下を featurespages で分離。
      • features: コンポーネント単位の実装を行う。
      • pages: 作成したコンポーネントを呼び出し、ページ単位での実装を行う。

Point2: features配下のディレクトリ整理

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

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

useReducerの活用

  • 課題

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

    • useReducerを活用する。
修正前
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>
  );
};
修正後
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のオブジェクト内に包括された。

修正後
  const [state, dispatch] = useReducer(slotMachineReducer, initialState);

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

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

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

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

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

まとめ

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

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?