넘블 챌린지 [다른 색깔 찾기 게임]
넘블 챌린지 [다른 색깔 찾기 게임]
상태관리 라이브러리 사용하지 않고 구현 하기
GitHub Repository
https://github.com/jaewoong2/no-state-management-challenge/tree/master
프로젝트 바로가기
https://no-state-management-challenge.vercel.app/
스타일 없는 프로젝트 바로가기
https://no-state-management-challenge.vercel.app/simple
상태 관리 없이?
App Component
에서Props
로 내려주는state
들을 어떻게 최적화 하여 보내줄지 고민하게 되었다
시도 방법
-
React.memo(Component, PropsAreEqual)
사용하기(실패)-
Header Component
에서 매초 남은 시간을 카운트 다운 해줄 떄마다Boards Component
가 무의미하게 리렌더링 되었다. -
PropsAreaEqaul
을 사용해서Boards Component
의 무의미한 리렌더링을 방지 하고자 하였다.React.memo(Board, (prevProps, nextProps) => prevProps.stage === nextProps.stage)
Boards Component
가stage가 바뀔 떄만 리렌더링 되게 하였다.
-
하지만, 리렌더링이 계속해서 되었다. 이유를 찾아보니 상위 컴포넌트의 리렌더링을 통한 자식 컴포넌트의 리렌더링은 막기 어렵다는 것이다.
(막는 방법이 있을 것 같긴 한데... 못찾음 )
-
-
useReducer
사용-
사실
reducer
보다useState
를 통한 상태 관리가 익숙하고 편리한데,reduecr
를 사용 한 이유는,Redux 와 같은 전역 상태 관리 라이브러리 를 사용하지 않기
가 이번 프로젝트의 목표 이기 떄문에Redux
와 비슷한 패턴을 갖는useReducer
를 사용 하면 상태 관리가 조금더 쉽지 않을까? 라는 생각에서 사용 하였다. -
컴포넌트들이 리렌더링 되는 것을 비교해보았는데,
useState
를 통한 상태관리와 별 차이는 없었던 것 같다. -
장점: 이번 프로젝트는
App Component
에서 모든 상태를 내려주어야 하는데useState
를 통한 상태관리는App.tsx
의 코드를 무의미하게 길어지게 만들었다. -
단점:
reduecr
파일을 따로 만들어야 하고, 조금 더 편하게 관리 하기 위해서reducer
의 코드가 무의미하게 길어지는 경향이 있다.
-
리팩토링 하며 알게된 부분
-
React.Fragment
<></>
사용 VS 기존 Html Element 사용<main></main>
-
React.Fragment 를 사용 하여 각각 컴포넌트를 감쌌을 때,
남은시간이 매초 변경 됨에 따라서 각각 컴포넌트가 모두 리랜더링 된다.
-
<main></main>
컴포넌트로 감싸 줬을 때,
남은시간이 매초 변경 됨에 따라서 각각 컴포넌트가 리랜더링 되는 대신에,
<main></main>
컴포넌트만 리랜더링 된다.<Header />
컴포넌트는 시간에 따라 바뀌어야 하기 때문에 정상적인 리렌더링
결론: main 컴포넌트 하나만 리렌더링이 되는게 좋은 것인지, 모든 하위 컴포넌트가 리렌더링 되는 것이 좋은 것인지 모르겠으나, 각 컴포넌트가 비용이 많이 든다면, React Fragment를 사용하지 않는 것이 좋은 것 같다. -
주요로직
색상 관련 코드
// @utils/index.ts
export const getColor = ({
r,
g,
b,
a,
weight,
}: {
r: number;
g: number;
b: number;
a: number;
weight: number;
}) => {
return `rgba(${r + weight}, ${g + weight}, ${b + weight}, ${a})`;
};
export const getRandomColor = (num: number) =>
num > 1 ? Math.floor(Math.random() * num) : Math.random() * num;
export const getRgba = () => ({
r: getRandomColor(255),
g: getRandomColor(255),
b: getRandomColor(255),
a: getRandomColor(0.7) + 0.3,
});
getColor
는rgba()
값으로 변경시켜주는 코드getRandomColor
는0 ~ 255
의 무작위 값을 반환 시켜주는 코드getRgba
는rgba
값을 객채화 시켜주는 코드<Boards />
의color State
에 사용 위함
// @components/Boards/Boards.tsx
useEffect(() => {
setColor({ ...getRgba(), weight: 100 - stage ** 0.7 * 6 });
}, [stage]);
stage
가 변경될 때마다 랜덤rgba
값을 얻는다.stage가
높아질 수록,weight가
줄어들면서 스테이지가 높을 수록 더욱더 찾기 어렵게 된다.
// @utils/index.ts
export const getNumbers = (stage: number) => {
const col = Math.round((stage + 0.5) / 2) + 1;
const area = Math.pow(col, 2);
const answer = Math.floor(Math.random() * area);
return {
col,
area,
answer,
};
};
// @components/Boards/Boards.tsx
const { col, answer, area } = useMemo(() => getNumbers(stage), [stage]);
stage
에 따른col: 가로 사각형 갯수
,area: 전체 사각형 갯수
,answer: 정답 index
를 얻는다.
수정전
// @components/Boards/Boards.tsx
const boardStyle = useCallback(
(isAnswer: boolean): React.CSSProperties => ({
backgroundColor: isAnswer
? `${getColor(color)}`
: `${getColor({ ...color, weight: 0 })}`,
}),
[color]
);
const boardWrapperStyle = useMemo(
(): React.CSSProperties => ({
gridTemplateColumns: `repeat(${col}, 1fr)`,
}),
[col]
);
stage
에 따른style
를 반환 해준다.
수정후
// @component/Boards/Boards.tsx
<Board
key={`Board-${i}-${v}`}
onClick={() => onClickBoard(i === answer)}
backgroundColor={
i === answer ? getColor(color) : getColor({ ...color, weight: 0 })
}
/>
// @components/Board/Board.style.tsx
import styled from "@emotion/styled";
import { BoardProps } from "./Board.type";
export const Li = styled.li<BoardProps>`
background-color: ${({ backgroundColor }) => backgroundColor};
cursor: pointer;
`;
// @components/Boards/Boards.style.tsx
import styled from "@emotion/styled";
export const Ul = styled.ul<{ col: number }>`
grid-template-columns: ${({ col }) => `repeat(${col}, 1fr)`};
`;
- tsx 컴포넌트 내에 간결한 코드를 위해서 수정 하였다.
코드 내에서 고려한 특정 유저 행동과 그에 대한 대처
weight: 100 - stage ** 0.7 * 6,
처음에는 weight
값을 100 - stage
로 했었는데, stage의 변화에 따라서 난이도가 눈에 띄게 변화하지 않았다.
stage
의 변화에 따라서 난이도를 올리 되, 정답을 잘 맞추는 유저의 경우 weight
값이 음수가 되는 것을 막기 위해서 100 - stage ** 0.7 * 6
값으로 하여 초반에 깰 때는 난이도를 올리다가, 어느정도 되면 난이도가 쉽게 바뀌지 않을 정도로 하였다.
활용한 라이브러리와 그 이유
- 활용한 라이브러리는 없으나
useReducer
의 훅을 사용 하였다. 이유는 앞서 작성한 것과 같다.
프로젝트를 진행할 때 어려웠던 점/고민했던 부분과 해결방법
-
프로젝트를 진핼 할 때,
Boards Component
의 불필요한 리렌더링을 어떻게 막을지가 가장 고민하였던 부분이다. -
앞서 작성 하였듯, 해결하지는 못했지만 많은 방법등을 사용해보았던 점에서 많은 경험을 하게 되었고
관심 컴포넌트 분리화
,전역 상태 관리 라이브러리
등이 왜 필요하게 되었는지 깨닫게 되었다.-
관심 컴포넌트 분리화
: 상위 컴포넌트에서 리렌더링 하면 하위 컴포넌트도 영향을 받기 때문에 최대한 분리화를 시켜서 불필요한 리렌더링을 막아야 한다. -
전역 상태 관리 라이브러리 사용
: 상위컴포넌트에서props
를 내리는 횟수를 줄이고, 유동적으로 상태를 관리를 함으로써 리렌더링을 막기 위함
-
Author And Source
이 문제에 관하여(넘블 챌린지 [다른 색깔 찾기 게임]), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jwisgenius/넘블-챌린지-다른-색깔-찾기-게임저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)