はじめに
前回までの記事の続きです。(シリーズ化予定)
▪️実装Step
Step1:基本編
Step2:Coin機能実装編
Step3: 仕様追加、リファクタリング実施
Step4:ワイルドカード、セブンカードの追加
Step5:ディレクトリ整理/リファクタリング ←いまここ
成果物
▪️実施内容
・ディレクトリ構成の整理
・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
配下を features と pages で分離。- features: コンポーネント単位の実装を行う。
- pages: 作成したコンポーネントを呼び出し、ページ単位での実装を行う。
-
Point2: features配下のディレクトリ整理
~/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の更新を一切意識する必要がなくなった。
const handleSpin = () => {
dispatch({type: SPIN_ACTION});
};
const handleCloseJackpot = () => {
dispatch({type: CLOSE_JACKPOT_ACTION});
};
まとめ
ディレクトリ構成、リファクタリングを実施した事で、責務の分離が一層やりやすくなりました!!!
責務の分離は保守性・可読性の向上はもちろんのこと、テストも実施しやすくなるため、是非試してみてください!!!!