들어가며,
Redux와 Context API를 비교하는 글을 작성했었다.
해당 글의 결론에서 Context API를 사용할 때 useState를 사용해서 만든 state를
Context.Provider의 value에 바로 넣을 경우 setState를 사용할 때마다 useContext를 사용하는
모든 자식 컴포넌트가 리렌더링되는 문제가 있었다.
이것을 해결하는 방법을 정리하겠다.
Context 상태 나누기
const counterReducer = (state, action) => {
switch (action.type) {
case "INCREMENT":
return {
number: state.number + 1,
};
case "DECREMENT":
return {
number: state.number - 1,
};
default:
return state;
}
};
const initialState = {
number: 0,
};
const CounterContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(counterReducer, initialState);
// ...
};
먼저 상태를 useReducer를 통해서 state와 dispatch로 나누었다.
여기서 굳이 useReducer를 사용하지 않고 useState를 사용해서 state와 setState를 사용해도
문제없다.
다만 본인은 참고를 하고 있는 블로그의 방식이 좋다고 생각해서 해당 방식으로 작성하고 있다.
( 참고 링크는 아래에 작성해두었다. )
const counterState = useState(0);
Context.Provider의 value에 넣기 위해서 하나의 state 방식을 사용했었는데,
이렇게 사용할 경우 state에 변화가 생겨 하나의 counterState가 같이 변경되므로 setState를
사용하는 컴포넌트도 리렌더링이 발생한다.
그래서 이것을 state와 dispatch ( useState라면 state, setState )로 나누었다.
const CounterContext = createContext(null);
const CounterDispatchContext = createContext(null);
const factoryUseContext = (name, context) => () => {
const ctx = useContext(context);
if (ctx === undefined)
throw new Error(
`This ${name}Context must be used withing a ${name}ContextProvider`
);
return ctx;
};
export const useCounterContext = factoryUseContext("Counter", CounterContext);
export const useCounterDispatchContext = factoryUseContext(
"CounterDispatch",
CounterDispatchContext
);
state와 dispatch로 나눈 것을 통해 눈치챈 분들도 있겠지만, state를 담기위한
CounterContext와 dispatch를 담기위한 CounterDispatchContext로 나누어서 관리하려고
한다.
이렇게 되면 state가 변경되더라도 실제 state를 의존하고 있는 컴포넌트만 리렌더링되고
dispatch를 사용한 컴포넌트는 dispatch의 변경을 감지하므로 리렌더링이 발생하지 않는다.
factoryUseContext는 useContext를 사용할 때 createContext를 사용한 값을 넣어줘야하는
불편한 점을 해결하기 위해서 사용한다.
또한 Context.Provider로 감싸지 않은 컴포넌트에서 해당 useContext를 호출하면 오류를
넘겨주므로 디버깅이 쉬워진다.
const CounterContextProvider = ({ children }) => {
console.log("Counter Context");
const [state, dispatch] = useReducer(counterReducer, initialState);
return (
<CounterContext.Provider value={state}>
<CounterDispatchContext.Provider value={dispatch}>
{children}
</CounterDispatchContext.Provider>
</CounterContext.Provider>
);
};
마지막으로 각각의 Context에 value를 넣어주면 끝이다.
dispatch를 사용하더라도 Control 컴포넌트는 리렌더링이 발생하지 않고,
View 컴포넌트만 리렌더링이 발생하는 것을 확인할 수 있다.
참고
https://velog.io/@shin6403/React-ContextAPI-%EC%9D%B4%EB%A0%87%EA%B2%8C-%EC%8D%A8%EB%B3%B4%EC%9E%90
https://blog.agney.dev/useMemo-inside-context/
'React > 실험실' 카테고리의 다른 글
[React] Recoil - 사용하기 (0) | 2022.11.10 |
---|---|
[React] Recoil - 시작하기 (0) | 2022.11.09 |
[React] Redux vs Context API (0) | 2022.11.04 |
[React] 제어 컴포넌트 vs 비제어 컴포넌트 (0) | 2022.11.03 |
[React] useMemo vs useRef (0) | 2022.11.02 |