같은글 노션링크 | 코드 변경 전후를 명확하게 보실수있습니다.
1.1 리팩토링 이유
3.1 게임페이지에서 src/page/GamePage.tsx
3.2 슬롯머신 커스텀 훅에서 src/hooks/useSlot.ts
데이터에 편리하게 접근하고 변경하기 위해 데이터를 저장하거나 조직하는 방법
1.1 리팩토링 이유
슬롯머신 기능을 구현하면서, 상수데이터인 MOVIE_SLOTOPTION를 가져다 쓸때마다 불편함을 느꼈다. 다시말해, 이 데이터를 쓰기 위해서 불필요한 코드들이 계속 추가됐다. 기능은 구현했으나, 찜찜했다.
함께 데이터를 가져다 쓸 동료들이 있다고 가정하고, 활용하기 편한 자료구조로 개선해보고자 한다.
배열형태로 따로따로 관리하던 상수데이터를 한 객체에 담아 다같이 관리한다. 객체 데이터에 접근할때는 Object.xx 메서드를 활용하면 도니다.
// src/constants/slotOption.ts
export const SLOT_MOVIE_COUNTRY: string[][] = [
['국내', 'K'],
['외국', 'F'],
];
export const SLOT_MOVIE_YEAR: string[][] = [
['2022', '2022'],
['2021', '2021'],
['2020', '2020'],
['2019', '2019'],
['2018', '2018'],
];
export const SLOT_MOVIE_TYPE: string[][] = [
['상업영화', 'N'],
['다양성영화', 'Y'],
];// 한 객체에 담았다.
export const MOVIE_SLOTOPTION = {
country: {
K: '국내',
F: '외국',
},
year: {
2022: '2022',
2021: '2021',
2020: '2020',
2019: '2019',
2018: '2018',
},
type: {
N: '상업영화',
Y: '다양성영화',
},
};3.1 src/page/GamePage.tsx_게임페이지
import {
SLOT_MOVIE_COUNTRY,
SLOT_MOVIE_TYPE,
SLOT_MOVIE_YEAR,
} from 'libs/constants/slotOptions';
export default function GamePage() {
const COUNTRY = 'country';
const YEAR = 'year';
const TYPE = 'type';
const option = { country: '', type: '', year: '' };
const constants = [
[COUNTRY, SLOT_MOVIE_COUNTRY],
[YEAR, SLOT_MOVIE_TYPE],
[YEAR, SLOT_MOVIE_YEAR],
];
...
return (
<section>
...
<div css={slot.container}>
<div css={slot.flexColumn}>
<div css={slot.spinningSquare}>
<Slot
name={COUNTRY}
option={SLOT_MOVIE_COUNTRY}
isFirstEntry={isFirstEntry}
isSpinning={isSpinning}
getSelectedOption={getSelectedOption}
css={slot.spinningText}
/>
</div>
<Button
aria-label={COUNTRY}
disabled={!isSpinning[COUNTRY]}
onClick={stopSpinning(COUNTRY)}
css={slot.button}
></Button>
</div>
<div css={slot.flexColumn}>
<div css={slot.spinningSquare}>
<Slot
name={YEAR}
option={SLOT_MOVIE_YEAR}
isFirstEntry={isFirstEntry}
isSpinning={isSpinning}
getSelectedOption={getSelectedOption}
css={slot.spinningText}
/>
</div>
<Button
aria-label={YEAR}
disabled={!isSpinning[YEAR]}
onClick={stopSpinning(YEAR)}
css={slot.button}
></Button>
</div>
<div css={slot.flexColumn}>
<div css={slot.spinningSquare}>
<Slot
name={TYPE}
option={SLOT_MOVIE_TYPE}
isFirstEntry={isFirstEntry}
isSpinning={isSpinning}
getSelectedOption={getSelectedOption}
css={slot.spinningText}
/>
</div>
<Button
aria-label={TYPE}
disabled={!isSpinning[TYPE]}
onClick={stopSpinning(TYPE)}
css={slot.button}
></Button>
</div>
</div>
</section>
);
}
MOVIE_SLOTOPION) 만 useSlot 훅에 넘겨주기만하면 된다.Slot 컴포넌트 로직이 3번 하드코딩 하던 것을 Object.entries와 map을 사용해 코드 길이를 줄였다. (가독성 up)
import { MOVIE_SLOTOPTION } from 'libs/constants/slotOptions';
export default function GamePage() {
const {
selected,
isFirstEntry,
isSpinning,
getSelectedOption,
startSpinning,
stopSpinning,
initEntryStateAndSelection,
} = useSlot({ slotOption: MOVIE_SLOTOPTION });
...
return (
<section>
...
<div css={slot.container}>
{Object.entries(MOVIE_SLOTOPTION).map(([name, value]) => {
return (
<div key={name} css={slot.flexColumn}>
<div css={slot.spinningSquare}>
<Slot
name={name}
option={value}
isFirstEntry={isFirstEntry}
isSpinning={isSpinning}
getSelectedOption={getSelectedOption}
css={slot.spinningText}
/>
</div>
<Button
aria-label={name}
disabled={!isSpinning[name]}
onClick={stopSpinning(name)}
css={slot.button}
></Button>
</div>
);
})}
</div>
</section>
);
}
3.2 src/hooks/useSlot.ts _슬롯머신 커스텀 훅
getSelectedOption 함수 사용시 interface Props {
option: Record<string, string>;
constants: (string | string[][])[][];
}
function useSlot({ option, constants }: Props) {
const initial = option;
const isSpin: Record<string, boolean> = {};
const keys = Object.keys(option);
keys.forEach(key => (isSpin[key] = false));
const [selected, setSelected] = useState(option);
const [isSpinning, setIsSpinning] = useState(isSpin);
const [isFirstEntry, setIsFirstEntry] = useState(true);
...
const getSelectedOption = (name: string, num: number) => { // 여기
const selectedCon = constants.filter(con => {
if (con[0] === name) return con[1];
})[0][1];
setSelected(prev => ({
...prev,
[name]: selectedCon[num][1],
}));
};
return {
...
};
}interface Props {
slotOption: Record<string, Record<string, string>>;
}
function useSlot({ slotOption }: Props) {
const initOption: Record<string, string> = {};
const isSpin: Record<string, boolean> = {};
const keys = Object.keys(slotOption);
keys.forEach(key => (initOption[key] = ''));
keys.forEach(key => (isSpin[key] = false));
const initial = { ...initOption };
const [selected, setSelected] = useState(initial);
const [isSpinning, setIsSpinning] = useState(isSpin);
const [isFirstEntry, setIsFirstEntry] = useState(true);
...
const getSelectedOption = (name: string, num: number) => { // 여기
const selected = Object.keys(slotOption[name])[num];
setSelected(prev => ({
...prev,
[name]: selected,
}));
};
...
return {
...
};
}
export default useSlot;