Agricola Project Context API 개선
작업 순서
1. context API를 잘 쓰는 방법에 대해 공부하기
2. 현재 코드에서 각 Context들이 다루고 있는 상태값이 무엇이 있는지 정리하기
3. 현재 코드에서 Context를 쓰고 있는 component와, 어떤 state를 사용하는지 정리하기
4. Context를 맥락에 맞게 사용하기 위해 어떻게 분리하고, 어떻게 씌워야할지 고민하기
Q. Context Provider를 만들고 여러 곳에서 사용하는 것은 문제가 안돼요?
네 !
React의 Context API와 Provider는 데이터를 여러 컴포넌트에 공유하고 전달하는 강력한 상태 관리 도구거든요 :)
다만 주의해야 할 점이 있다면,
1. 필요한 곳에만 사용하기
: context에 있는 값이 바뀌면 그 안에 있는 컴포넌트들이 모두 리렌더링 되기 때문에 불필요한 컴포넌트를 감싸면 불필요한 리렌더링이 발생하게 된다.
2. 컴포넌트 트리 구조를 보며 [부모~>자식]으로 흐르는 데이터 흐름을 발견하고, 적절한 그룹으로 묶어서 Provider를 씌우기
: (사실 1번하고 크게 다른 이야기가 아닌 것 같네 ..ㅎ)
: 각 컴포넌트의 역할을 파악 하고, 컴포넌트간의 관계 형성에 주목하면 컴포넌트 트리 구조를 그리기에 좋다 !
Q. 커스텀훅에서 context 값을 쓰고 있다면 어떻게 되는거지?
커스텀훅을 호출한 컴포넌트 관점으로 옮겨가게 되는 것이지!
커스텀훅에서 context A 값을 가지고 있고, A값이 변경될 때에는 이 커스텀 훅을 사용하는 컴포넌트가 다시 그려지는거야.
현재 코드 분석
AuthContext.jsx
< 사용되는 곳 >
ActionBoard.jsx const { pid, setIsFbActive, isAbActive, setIsAbActive, setIsCsActive } = useAuthContext();
Card.jsx const { setIsAbActive, setIsCsActive, pid } = useAuthContext();
CardSlodBoard.jsx const { isCsActive } = useAuthContext();
FarmBoard.jsx const { isFbActive } = useAuthContext();
Land.jsx const { setIsFbActive, setIsAbActive } = useAuthContext();
Pen.jsx const { setIsFbActive, setIsAbActive } = useAuthContext();
Prompt.jsx const { pid, setPid } = useAuthContext();
BackgroundContext.jsx
어우 .. 이거 엉망인데 ........ ?
그래서 엑셀로 분석해봤다.
그 결과 BackgroundContext에서 관리하던 여러 성질의 context들을 공통점 기준으로 묶어 새로운 파일 단위로 분리하기 했다. 아래는 새로 생성된 두개의 context이다.
- IsActiveContext.jsx
: 게임 진행 시 제어용으로 화면의 각 영역을 활성화/비활성화 시키기 위해 관리하는 context이다.
import { createContext, useContext, useState } from 'react';
export const IsActiveContext = createContext();
export function IsActiveContextProvider({ childern }) {
const [isActionBoardActive, setIsActionBoardActive] = useState(true);
const [isFarmBoardActive, setIsFarmBoardActive] = useState(false);
const [isCardSlotActive, setIsCardSlotActive] = useState(false);
const [isMainCardActive, setIsMainCardActive] = useState(false);
const [isSubCardActive, setIsSubCardActive] = useState(false);
const [isJobCardActive, setIsJobCardActive] = useState(false);
const activateActionBoard = () => {
setIsActionBoardActive(true);
setIsFarmBoardActive(false);
setIsCardSlotActive(false);
};
const activateFarmBoard = () => {
setIsActionBoardActive(false);
setIsFarmBoardActive(true);
setIsCardSlotActive(false);
};
const activateCardSlot = (type) => {
setIsActionBoardActive(false);
setIsFarmBoardActive(false);
setIsCardSlotActive(true);
switch (type) {
case 'main':
setIsMainCardActive(true);
break;
case 'sub':
setIsSubCardActive(true);
break;
case 'job':
setIsJobCardActive(true);
break;
default:
throw new Error('card type이 잘못되었음');
}
};
const initCardActive = () => {
setIsMainCardActive(false);
setIsSubCardActive(false);
setIsJobCardActive(false);
};
// card 컴포넌트에 handleClickPossible context로 빼기
const isCardClickPossible = (id) => {
if (0 < id && id <= 14) return isJobCardActive;
else if (14 < id && id <= 28) return isSubCardActive;
else return isMainCardActive;
};
return (
<IsActiveContext.Provider
value={{
isActionBoardActive,
isFarmBoardActive,
isCardSlotActive,
activateFarmBoard,
activateActionBoard,
activateCardSlot,
initCardActive,
isCardClickPossible,
}}
>
{childern}
</IsActiveContext.Provider>
);
}
export function useIsActiveContext() {
return useContext(IsActiveContext);
}
- OpenCardContext.jsx
: 카드 슬롯이 열리고 닫히는 부분을 context로 관리하고 있다. 사실 이 방법 말고 더 좋은 방법이 있을 것 같긴 한데 .. 아직 잘 모르겠다 .. ㅎ
import { createContext, useContext, useState } from 'react';
export const OpenCardContext = createContext();
export function OpenCardProvider({ children }) {
const [isMajorCardSlotOpen, setIsMajorCardSlotOpen] = useState(false);
const [isP1HaveCardSlotOpen, setIsP1HaveCardSlotOpen] = useState(false);
const [isP1ActivatedCardSlotOpen, setIsP1ActivatedCardSlotOpen] =
useState(false);
const [isP2HaveCardSlotOpen, setIsP2HaveCardSlotOpen] = useState(false);
const [isP2ActivatedCardSlotOpen, setIsP2ActivatedCardSlotOpen] =
useState(false);
const openMajorCardSlot = () => {
setIsMajorCardSlotOpen(true);
setIsP1HaveCardSlotOpen(false);
setIsP1ActivatedCardSlotOpen(false);
setIsP2HaveCardSlotOpen(false);
setIsP2ActivatedCardSlotOpen(false);
};
const closeMajorCardSlot = () => {
setIsMajorCardSlotOpen(false);
setIsP1HaveCardSlotOpen(false);
setIsP1ActivatedCardSlotOpen(false);
setIsP2HaveCardSlotOpen(false);
setIsP2ActivatedCardSlotOpen(false);
};
const openP1HaveCardSlot = () => {
setIsMajorCardSlotOpen(false);
setIsP1HaveCardSlotOpen(true);
setIsP1ActivatedCardSlotOpen(false);
setIsP2HaveCardSlotOpen(false);
setIsP2ActivatedCardSlotOpen(false);
};
const closeP1HaveCardSlot = () => {
setIsMajorCardSlotOpen(false);
setIsP1HaveCardSlotOpen(false);
setIsP1ActivatedCardSlotOpen(false);
setIsP2HaveCardSlotOpen(false);
setIsP2ActivatedCardSlotOpen(false);
};
const openP1ActivatedCardSlot = () => {
setIsMajorCardSlotOpen(false);
setIsP1HaveCardSlotOpen(false);
setIsP1ActivatedCardSlotOpen(true);
setIsP2HaveCardSlotOpen(false);
setIsP2ActivatedCardSlotOpen(false);
};
const closeP1ActivatedCardSlot = () => {
setIsMajorCardSlotOpen(false);
setIsP1HaveCardSlotOpen(false);
setIsP1ActivatedCardSlotOpen(false);
setIsP2HaveCardSlotOpen(false);
setIsP2ActivatedCardSlotOpen(false);
};
const openP2HaveCardSlot = () => {
setIsMajorCardSlotOpen(false);
setIsP1HaveCardSlotOpen(false);
setIsP1ActivatedCardSlotOpen(false);
setIsP2HaveCardSlotOpen(true);
setIsP2ActivatedCardSlotOpen(false);
};
const closeP2HaveCardSlot = () => {
setIsMajorCardSlotOpen(false);
setIsP1HaveCardSlotOpen(false);
setIsP1ActivatedCardSlotOpen(false);
setIsP2HaveCardSlotOpen(false);
setIsP2ActivatedCardSlotOpen(false);
};
const openP2ActivatedCardSlot = () => {
setIsMajorCardSlotOpen(false);
setIsP1HaveCardSlotOpen(false);
setIsP1ActivatedCardSlotOpen(false);
setIsP2HaveCardSlotOpen(false);
setIsP2ActivatedCardSlotOpen(true);
};
const closeP2ActivatedCardSlot = () => {
setIsMajorCardSlotOpen(false);
setIsP1HaveCardSlotOpen(false);
setIsP1ActivatedCardSlotOpen(false);
setIsP2HaveCardSlotOpen(false);
setIsP2ActivatedCardSlotOpen(false);
};
return (
<OpenCardContext.Provider
value={{
isMajorCardSlotOpen,
isP1HaveCardSlotOpen,
isP1ActivatedCardSlotOpen,
isP2HaveCardSlotOpen,
isP2ActivatedCardSlotOpen,
openMajorCardSlot,
closeMajorCardSlot,
openP1HaveCardSlot,
closeP1HaveCardSlot,
openP1ActivatedCardSlot,
closeP1ActivatedCardSlot,
openP2HaveCardSlot,
closeP2HaveCardSlot,
openP2ActivatedCardSlot,
closeP2ActivatedCardSlot,
}}
>
{children}
</OpenCardContext.Provider>
);
}
export function useOpenCardContext() {
return useContext(OpenCardContext);
}
결론
- 짬뽕으로 섞인 context 파일 속 데이터를 엑셀로 정리하고, 사용되는 곳을 추적하니 어떻게 그룹을 만들어야 하는지 판단할 수 있었다. 객관적으로 상황을 분석하는 것이 도움이 됨 👍🏻
- 프로젝트 상황으로 context API만 사용하였는데, 다음에 프로젝트를 한다면 zustand를 써보고 싶다.