JoungSik/React Context API TypeScript 로 사용하기

Created Tue, 23 Nov 2021 23:59:59 +0900 Modified Sat, 31 Dec 2022 01:31:35 +0900

React Context API TypeScript 로 사용하기

원래 저는 React 를 사용할때 전역 상태 값 관리를 Redux 와 Redux-saga 를 사용하고 있었습니다.

그런데 이번에 React-Query 를 사용하게 되면서 API 결과는 React-Query 를 쓰게되었고 내부에서 사용하는 전역 상태 값을 관리하기에 Redux 는 생각이상으로 보일러플레이트 코드가 많아 전부터 사용하고 싶었던 Context API를 사용해보기 위한 코드를 작성하면서 메모를 하기 위해 글을 작성하게 되었습니다.

사용

별도의 설치는 따로 없습니다.

yarn create react-app sample --template typescript

context api 내용이 적혀 있는 파일을 먼저 생성합니다.

sample.tsx

import React, { useReducer, useContext, createContext, Dispatch } from 'react';

// 필요한 타입들을 미리 선언

type Color = 'red' | 'orange' | 'yellow';

// 상태를 위한 타입
type State = {
  count: number;
  text: string;
  color: Color;
  isGood: boolean;
};

// 모든 액션들을 위한 타입
type Action =
  | { type: 'SET_COUNT'; count: number }
  | { type: 'SET_TEXT'; text: string }
  | { type: 'SET_COLOR'; color: Color }
  | { type: 'TOGGLE_GOOD' };

// 디스패치를 위한 타입 (Dispatch 를 리액트에서 불러올 수 있음), 액션들의 타입을 Dispatch 의 Generics로 설정
type SampleDispatch = Dispatch<Action>;

// Context 만들기
const SampleStateContext = createContext<State | null>(null);
const SampleDispatchContext = createContext<SampleDispatch | null>(null);

// 리듀서
function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'SET_COUNT':
      return {
        ...state,
        count: action.count // count가 자동완성되며, number 타입인걸 알 수 있습니다.
      };
    case 'SET_TEXT':
      return {
        ...state,
        text: action.text // text가 자동완성되며, string 타입인걸 알 수 있습니다.
      };
    case 'SET_COLOR':
      return {
        ...state,
        color: action.color // color 가 자동완성되며 color 가 Color 타입인걸 알 수 있습니다.
      };
    case 'TOGGLE_GOOD':
      return {
        ...state,
        isGood: !state.isGood
      };
    default:
      throw new Error('Unhandled action');
  }
}

// SampleProvider 에서 useReduer를 사용하고
// SampleStateContext.Provider 와 SampleDispatchContext.Provider 로 children 을 감싸서 반환합니다.
export function SampleProvider({ children }: { children: React.ReactNode }) {
  const [state, dispatch] = useReducer(reducer, {
    count: 0,
    text: 'hello',
    color: 'red',
    isGood: true
  });

  return (
    <SampleStateContext.Provider value={state}>
      <SampleDispatchContext.Provider value={dispatch}>
        {children}
      </SampleDispatchContext.Provider>
    </SampleStateContext.Provider>
  );
}

// state 와 dispatch 를 쉽게 사용하기 위한 커스텀 Hooks
export function useSampleState() {
  const state = useContext(SampleStateContext);
  if (!state) throw new Error('Cannot find SampleProvider'); // 유효하지 않을땐 에러를 발생
  return state;
}

export function useSampleDispatch() {
  const dispatch = useContext(SampleDispatchContext);
  if (!dispatch) throw new Error('Cannot find SampleProvider'); // 유효하지 않을땐 에러를 발생
  return dispatch;
}

context api 코드를 불러와 전체 코드에 적용시킵니다.

App/tsx

import React from 'react';
import { SampleProvider } from './sample';
import Home from './home';

function App() {
  return (
    <SampleProvider>
      <Home />
    </SampleProvider>
  )
};

export default App;

실제 사용하는 코드가 적혀 있는 home.tsx 파일을 생성합니다.

import React from 'react';
import { useSampleState, useSampleDispatch } from './sample';

const Home = () => {
  const state = useSampleState();
  const dispatch = useSampleDispatch();

  const setCount = () => dispatch({ type: 'SET_COUNT', count: 5 });
  const setText = () => dispatch({ type: 'SET_TEXT', text: 'bye' });
  const setColor = () => dispatch({ type: 'SET_COLOR', color: 'orange' });
  const toggleGood = () => dispatch({ type: 'TOGGLE_GOOD' });

  return (
    <div>
      <p>
        <code>count: </code> {state.count}
      </p>
      <p>
        <code>text: </code> {state.text}
      </p>
      <p>
        <code>color: </code> {state.color}
      </p>
      <p>
        <code>isGood: </code> {state.isGood ? 'true' : 'false'}
      </p>
      <div>
        <button onClick={setCount}>SET_COUNT</button>
        <button onClick={setText}>SET_TEXT</button>
        <button onClick={setColor}>SET_COLOR</button>
        <button onClick={toggleGood}>TOGGLE_GOOD</button>
      </div>
    </div>
  )
};

export default Home;

위 코드를 SpinBox 컴포넌트로 응용해보겠습니다.

응용

sample.tsx 는 사용하지 않는 타입을 삭제하고 initialState 을 추가했습니다.

const initialState = {
  count: 0
};

home.tsx

import React from 'react';
import { useSampleState, useSampleDispatch } from './sample';

const Home = () => {
  const state = useSampleState();
  const dispatch = useSampleDispatch();

  const onIncrease = () => dispatch({ type: 'SET_COUNT', count: state.count + 1 });
  const onDecrease = () => dispatch({ type: 'SET_COUNT', count: state.count - 1 });

  return (
    <div>
      <button onClick={onDecrease}>-</button>
      <span>{state.count}</span>
      <button onClick={onIncrease}>+</button>
    </div>
  )
};

export default Home;

디버깅 도구로 React Context DevTool 을 사용합니다.

1

이전에 쓰던 Redux 처럼 사용할수록 점점 뭔가 더 붙어서 편하게 사용하려고 할 것 같지만 지금은 이정도로 만족하고 있습니다.

참고

https://react.vlpt.us/using-typescript/04-ts-context.html