본문 바로가기
React/실험실

[React] Context API 사용하기

by 잉여개발자 2022. 11. 5.

들어가며,

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

 

React | ContextAPI 이렇게 써보자

어느날 구글링을 하다가 어떠한 글을 보았다.Context API는 왜 안쓰나요?ContextAPI를 쓰는 글쓴이에게 굉장히 관심이 가는 글이었고, 내용을 결론은 소규모 프로젝트에서는 ContextAPI가 좋지만 성능 때

velog.io

https://blog.agney.dev/useMemo-inside-context/

 

The Mindless - Boy with Silver Wings

One of the most common places a useMemo is seen (in the wild) is for memoizing React context value. Let’s look into what are the advantages of doing this and how we can prevent unnecessary renders using this pattern. Context provides a way to pass data t

blog.agney.dev

 

반응형

'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

댓글