2022.02.10 - [Frontend/React] - 넘블챌린지 [Square Select Game]
이때 피드백 받은 내용과 모범답안을 기반으로 현재의 코드를 리팩토링 해보았다.
그때 얻은 인사이트를 기록해보려 한다.
아쉬운 부분에 대한 의견을 들었을때 머리가 띵했다...
이제까지 이런식으로 작성했었고 나는 계속 이런식으로 봐왔으니까 가독성이 좋다고 생각했었다..... 완전히 우물안의 개구리 였던것.....
인수인계 했을때 읽히기 쉬운 코드를 작성하자는 신념이 있었는데 내 코딩 패턴은 전혀 그러지 못했었던 것.....
만약 프로젝트가 조금만 더 규모가 커졌었더라면 저 state와 handler를 정의하는 부분은 감당이 안되고 어디를 어떻게 바꾸어야 하는지를 도저히 알수가 없었을 것이다.... (쉽지 않다....)
피드백 내용을 기반으로 크게 두가지가 디테일한 문제라 판단하였다.
1. 무분별한 state, handler 추가
2. handler를 넘겨줄때 추상화레벨을 하나 높여서 전달하는것이 좋지 않았을까 싶다. (현재는 코드를 읽는데 개인의 해석이 크게 들어가야 한다. 어지럽다....)
해결책
1 => 선언된 state들은 게임이라는 하나의 주제에 속한다. 하나의 객체 안에서 key값들로 관리해줄수 있을것만 같다.
그렇다면 하나의 주제에 여러개의 상태가 필요하다. => useReducer 훅 적용, custom hook을 이용한 캡슐화 적용
2 => dispatch를 이용하는 함수를 정의해서 넘겨줄것이다.
- game 관련 정보를 다루는 모든 함수는 game 정보가 정의된 곳에 같이 위치시킬 것이다. => game 정보를 코드레벨에서 수정한다면 높은확률로 함수들도 수정이 이뤄질 수 있을 것이다.
react 공식문서에는 useReducer에 대해 다음과 같이 나와있다.
useState의 대체 함수 입니다. // ....중략
다수의 하윗값을 포함하는 복잡한 정적 로직을 만드는 경우나 다음 state가 이전 state에 의존적인 경우에 보통 useState보다 useReducer를 선호합니다. 또한 useReducer는 자세한 업데이트를 트리거 하는 컴포넌트의 성능을 최적화할 수 있게 하는데, 이것은 콜백 대신 dispatch를 전달 할 수 있기 때문입니다.
!!!하나의 주제에 다양한 액션을 정의해야 한다면 앞으로 useReducer를 활용해 보려 한다.
커스텀 훅에서 state와 필요한 action만 넘겨주게 하였다.
또한 컴포넌트에 props를 넘겨줄때 스프레드 연산자를 사용해서 넘겨주었다.
이 부분은 어떤 props 들이 넘어가는지 명시해주는것도 좋을것 같긴한데 컴포넌트에서 관리하는 걸로 넘겼다.
나중에 이것에 관하여 좀 찾아봐야겠다.
onSelect라는 naming으로 넘겨주어서 코드를 읽을때 동작을 연상시킬수 있다. (멘토님 좀 짱인듯....)
아래는 커스텀 훅의 내용이다.
useReducer를 사용하였고 useEffect을 이용해 필요한 state들만 관리할 수 있도록 하였다.
import { useInterval } from "./useInterval";
import { useEffect, useReducer } from "react";
import {
getAnswerIndex,
getBlockColors,
getPlusScore,
getResultText,
} from "src/utils";
type GameState = {
stage: number;
remainSeconds: number;
score: number;
answerIndex: number;
isRunning: boolean;
answerTileColor: string;
wrongTileColor: string;
};
const { answerTileColor, wrongTileColor } = getBlockColors(0);
const getInitialState = (): GameState => ({
stage: 1,
remainSeconds: 15,
score: 0,
answerIndex: getAnswerIndex(1),
isRunning: true,
answerTileColor,
wrongTileColor,
});
type GameActionType = "INIT" | "SELECT_ANSWER" | "SELECT_WRONG" | "TICK";
type GameAction = { type: GameActionType };
const reducer = (prev: GameState, action: GameAction) => {
const { answerTileColor, wrongTileColor } = getBlockColors(prev.stage);
switch (action.type) {
case "INIT":
return getInitialState();
case "SELECT_ANSWER":
return {
...prev,
stage: prev.stage + 1,
answerIndex: getAnswerIndex(prev.stage),
remainSeconds: 15,
score: getPlusScore(prev.stage, prev.remainSeconds),
answerTileColor,
wrongTileColor,
};
case "SELECT_WRONG":
return {
...prev,
remainSeconds: prev.remainSeconds - 3 > 0 ? prev.remainSeconds - 3 : 0,
};
case "TICK":
if (!prev.isRunning) {
return prev;
}
if (prev.remainSeconds <= 0) {
return {
...prev,
isRunning: false,
};
} else {
return {
...prev,
remainSeconds: prev.remainSeconds - 1,
};
}
default:
return prev;
}
};
export const useGame = () => {
const [state, dispatch] = useReducer(reducer, getInitialState());
useInterval(() => {
dispatch({ type: "TICK" });
}, 1000);
useEffect(() => {
if (!state.isRunning) {
alert(getResultText(state.stage, state.score));
dispatch({ type: "INIT" });
}
}, [state.isRunning]);
const select = (questionIndex: number) => {
if (questionIndex === state.answerIndex) {
dispatch({ type: "SELECT_ANSWER" });
} else {
dispatch({ type: "SELECT_WRONG" });
}
};
return {
state,
select,
};
};
useReducer를 처음 사용해봤는데 공식문서에 counter 예제가 잘 나와있어 적용하는데는 어렵지 않았다.
고민었던 것은 dispatch를 아예 export 시켜버려서 외부에서 직접 dispatch를 할 수 있게 하였었는데
작업 수정시 왔다갔다 해야하는 경우가 생겼었다.
그래서 커스텀훅 내에서 함수를 하나 정의해 주었고 그 함수를 외부 컴포넌트에서 전달받아 사용하게 하였다.
결론
구현해야 하는 범위가 크지 않았지만 고려해야 하는 것들은 결코 작지 않았다.
useReducer의 존재에 대해 알았고 어떤때에 어떻게 써야하는지,
state는 어떻게 결정하는 것이 좋은지,
setInterval를 react에서 무지성으로 사용하면 어떻게 되는지,
등등 크고 작은것들에 대해 인지하고 고려하게 되었다.
배운게 많아 후회하지 않는 경험이였다.
https://github.com/woobottle/square-select-game/tree/refeactoring
https://ko.reactjs.org/docs/hooks-reference.html#usereducer
https://sumini.dev/retrospective/002-numble-thinking-in-react/
'Frontend > React' 카테고리의 다른 글
React useCallback, useMemo (0) | 2022.03.27 |
---|---|
React 의존성 배열 🌱 (0) | 2022.03.27 |
React useLayoutEffect (0) | 2022.03.03 |
제어 컴포넌트 vs 비제어 컴포넌트 (0) | 2022.03.03 |
React twice render (0) | 2022.02.24 |