Web/React

[React] Context API를 통한 전역 상태 관리 (with Typescript)

레에몽 2022. 2. 16. 15:41

Context API로 상태관리를 해보자 😁

이번 글은 전역적으로 상태를 관리하는 방법 중 리액트 자체에서 제공하는 Context API를 사용해서 다루어 보는 법을 알려드리려고 합니다.

 

Context API가 뭔데요?

일반적인 React 애플리케이션에서 데이터는 위에서 아래로 (즉, 부모로부터 자식에게) props를 통해 전달됩니다(props drilling).

 

애플리케이션 안의 여러 컴포넌트들에 전해줘야 하는 props의 경우 이 과정이 번거로울 수 있습니다.

 

Context API를 이용하면, 트리 단계마다 명시적으로 props를 넘겨주지 않아도 많은 컴포넌트가 이러한 값을 공유하도록 할 수 있습니다.

 

쉽게 풀어서 얘기하자면, 위에서 아래로 주면서 props가 너무 많이 전달되는 현상을 없애기 위해서 나왔다고 할 수 있습니다.

 

언제 Context API를 사용하면 좋을까요?

컴포넌트의 계층적인 관계가 너무 많거나, 너무 많은 데이터를 전달하는 경우 데이터를 전달하는 법이라고 리액트에서 명시합니다.

그러나 Context API를 사용하게 된다면 재사용하기가 어려워지므로 꼭 필요할 때만 쓰길 권장드립니다 :)

 

실제 사용법

Context를 만들자 !

전역적으로 사용할 수 있는 하나의 장치를 만들어야 합니다. 해당 부분은 전역적으로 사용해야 할 수 있으므로, 필요한 컴포넌트들의 레벨을 고려해서 사용하는 것이 좋겠죠?

 

const Context = createContext(defaultValue);

<Context.Provider value={number}>{children}</Context.Provider>;

의 방식으로 사용한다면 children에 해당하는 컴포넌트들은 전역적으로 number를 가져다 쓸 수 있습니다.

 

그렇다면 사용해야 할 컴포넌트, 즉 children 가장 최소 공통 부모에 적용시키는 것이 좋습니다.

 

Context API를 userReducer를 통해 응용을 해보자 😎

해당 방식을 응용하면 리덕스와 비슷하게 사용할 수 있습니다.

 

const DispatchContext = createContext(defaultDispatch);

<DispatchContext.Provider value={dispatch}>{children}<DispatchContext.Provider />

의 방식으로 dispatch또한 전역적으로 가져다 쓸 수도 있습니다.

위에 있는 코드와 합치게 된다면 해당 방식으로 적을 수 있습니다.

 

const [number, dispatch] = useReducer(numberReducer, defaultState);

<Context.Provider value={number}>
  <DispatchContext.Provider value={dispatch}>
    {children}
  </DispatchContext.Provider>
</Context.Provider>;

이처럼 Context API를 겹겹이 쌓아서도 사용이 가능합니다.

 

Context API의 단점은 뭔가요?

Provider 컴포넌트는 value prop을 받아서 하위 컴포넌트에게 전달합니다. 그렇다면 value가 업데이트 되었을 때 리렌더링 되어야 하는 것들은 어디까지 리렌더링이 될까요? value를 직접적으로 쓰고 있는 Component?

 

실제로는 모든 컴포넌트가 다시 리렌더링이 됩니다.

 

Provider로부터 하위 consumer로의 전파는 shouldComponentUpdate메서드가 적용되지도 않고, 상위 컴포넌트가 업데이트를 건너 뛰더라도 consumer가 업데이트 됩니다.

 

그래서 전역적으로 상태관리라는 측면보다는, 전역적으로 상태를 사용할 수 있다. 정도가 제일 적합하지 않을까라고 생각이 듭니다.

 

숫자를 변경하는 Context API Code

실제로 코드로 만들어 봅시다. 전역적으로 숫자를 사용하는 정말 간단한 Context를 만들어 봅시다.

 

아래 코드의 전체 부분은 해당 레포지토리에서 확인할 수 있습니다. 깃헙링크 바로가기

 

구상

  • Header에서 숫자를 늘리거나 줄일 수 있게하면서 숫자를 보여준다.
  • Body에서는 Header와 같은 숫자를 보여준다.
  • Footer에서는 숫자를 0으로 만들 수 있는 버튼을 넣는다.

 

Context 생성

더보기
const initialState = {
  type: '',
  number: 0,
};

const numberReducer = (state: State, action: NumberAction): any => {
  switch (action.type) {
    case 'UP':
      return { ...state, number: action.payload };
    case 'DOWN':
      return { ...state, number: action.payload };
    case 'ZERO':
      return { ...state, number: action.payload };
    default:
      break;
  }
};

export const NumberStateContext = createContext<State | null>(null);
export const NumberDispatchContext = createContext<NumberDispatch | null>(null);

export const NumberProvider: React.FC<INumberProvider> = ({ children }) => {
  const [number, dispatch] = useReducer(numberReducer, initialState);

  return (
    <NumberStateContext.Provider value={number}>
      <NumberDispatchContext.Provider value={dispatch}>
        {children}
      </NumberDispatchContext.Provider>
    </NumberStateContext.Provider>
  );
};

 

상태를 쉽게 가져다 쓸 수 있게 커스텀 유틸 만들기

더보기
import { useContext } from 'react';
import { NumberDispatchContext, NumberStateContext } from './NumberContext';

export const useNumberState = () => {
  const state = useContext(NumberStateContext);
  if (!state) console.log('error');
  return state;
};

export const useNumberDispatch = () => {
  const dispatch = useContext(NumberDispatchContext);
  if (!dispatch) console.log('error');
  return dispatch;
};

 

Provider를 통해 전역 Context

더보기
function App() {
  return (
    <NumberProvider>
      <div className='app'>
        <Header />
        <Body />
        <Footer />
      </div>
    </NumberProvider>
  );
}

 

Header Component

 여기서는 숫자와, +1 -1 함수를 만든다.

더보기
const Header: React.FC = () => {
  const { number } = useNumberState() as State;
  const dispatch = useNumberDispatch() as NumberDispatch;

  const handleUpClick = (
    e: React.MouseEvent<HTMLButtonElement, MouseEvent>
  ) => {
    dispatch({ type: 'UP', payload: number + 1 });
  };

  const handleDownClick = (
    e: React.MouseEvent<HTMLButtonElement, MouseEvent>
  ) => {
    dispatch({ type: 'DOWN', payload: number - 1 });
  };

  return (
    <header className='header-container'>
      <h2> Welcome to Number Change with CONTEXT API</h2>
      <h3> {number} </h3>
      <div className='header-btn'>
        <button onClick={handleUpClick}>UP</button>
        <button onClick={handleDownClick}>DOWN</button>
      </div>
    </header>
  );
};

 

Body Component

 단순하게 숫자만 보여준다.

더보기
const Body: React.FC = () => {
  const { number } = useNumberState() as State;

  return (
    <div className='body-container'>
      <h2> Body </h2>
      <h3> {number} </h3>
    </div>
  );
};

 

Footer Component

 0으로 만드는 함수를 만든다.

더보기
const Footer: React.FC = () => {
  const dispatch = useNumberDispatch() as NumberDispatch;

  const handleZeroClick = (
    e: React.MouseEvent<HTMLButtonElement, MouseEvent>
  ) => {
    dispatch({ type: 'ZERO', payload: 0 });
  };

  return <button onClick={handleZeroClick}>Make Zero</button>;
};

 

실행화면

연동이 잘 되는 모습을 볼 수 있다.