[React에서 TypeScript사용하기] #4 Context API

mechaniccoder·2020년 7월 17일
0

React + TypeScript

목록 보기
4/5
post-thumbnail

프로젝트 준비


velopert님의 벨로그 포스트를 보면서 이번 장에서는 타입스크립로 리액트의 Context API를 활용하는 방법에 대해서 알아보겠습니다. 프로젝트의 Hello, World!라고 할 수 있는 투두리스트를 만들어보면서 배웠던 내용을 정리해보죠.

먼저, 타입스크립트가 적용된 리액트 프로젝트를 만들어야겠죠?

$ yarn create react-app ts-context-api --template typescript

리액트의 자체 상태관리인 context API를 만들때는 크게 4가지로 구분할 수 있습니다.

  1. 상태관리 context
  2. action 타입선언 + dispatch context
  3. 리듀서 함수 작성
  4. Provider 감싸기

상태관리 context

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

export type Todo = {
  id: number;
  text: string;
  done: boolean;
};

type TodosState = Todo[];

const TodosStateContext = createContext<TodosState | undefined>(undefined);

여기서 초기 상태는 Todo[] 혹은 아직 정의되지 않았을 수도 있기 때문에 undefined를 타입으로 추가해줍니다.

dispatch context

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

type Action =
  | {type: "CREATE"; text: string}
  | {type: "TOGGLE"; id: number}
  | {type: "REMOVE"; id: number};

type TodosDispatch = Dispatch<Action>; // 이 부분!
const TodosDispatchContext = createContext<TodosDispatch | undefined>(
  undefined
);

여기서 Dispatch<Action>와 같이 제네릭으로 액션들의 타입을 넣으면 나중에 컴포넌트에서 액션을 디스패치할 때 타입 검사를 할 수 있습니다. 정확히 동작하는 방식은 잘 모르겠지만 일단은 알아둡시다!

리듀서 함수 작성

function todosReducer(state: TodosState, action: Action): TodosState {
  switch (action.type) {
    case "CREATE":
      const nextId = Math.max(...state.map((todo) => todo.id)) + 1;
      const newState = state.concat({
        id: nextId,
        text: action.text,
        done: false,
      });
      return newState;
    case "TOGGLE":
      return state.map((todo) =>
        todo.id === action.id ? {...todo, done: !todo.done} : todo
      );
    case "REMOVE":
      return state.filter((todo) => todo.id !== action.id);
    default:
      throw new Error("Unahdled action");
  }
}

이 부분에서 눈 여겨봐야할 부분은 state.map이 아니라 ...state.map을 사용했다는 점입니다. 콘솔에 찍어본 결과 배열을 리턴하는 전자와는 다르게 ...state.map을 사용했을 경우 배열을 벗겨서 값 자체를 리턴해줍니다. 알아두면 나중에 요긴하게 써먹을 것 같죠?

Provider 감싸기

위에서 만든 상태 context와 dispatch context 각각의 provider를 합쳐보죠.

export function TodosContextProvider({children}: {children: React.ReactNode}) {
  const [todos, dispatch] = useReducer(todosReducer, [
    {
      id: 1,
      text: "Context API 배우기",
      done: true,
    },
    {
      id: 2,
      text: "TypeScript 배우기",
      done: true,
    },
    {
      id: 3,
      text: "TypeScript 와 Context API 함께 사용하기",
      done: false,
    },
  ]);

  return (
    <TodosDispatchContext.Provider value={dispatch}>
      <TodosStateContext.Provider value={todos}>
        {children}
      </TodosStateContext.Provider>
    </TodosDispatchContext.Provider>
  );
}

children은 App.js혹은 Index.js에서 렌더링할 컴포넌트를 말합니다. Provider로 감싸야되기 때문이죠.

이렇게 context API에 대해서 알아봤는데 커스텀 hooks을 사용하면 나중에 컴포넌트에서 사용할 때 더 편하게 상태관리를 할 수 있습니다. 특히, todos는 타입이 두 개였죠? TodosState | undefined 이렇게 두 개였는데 이를 사용하기 전에 꼭 유효성 검사를 해줘야합니다.

const todos = useContext(TodosStateContext);
if (!todos) return null;

그래서 이런 부분때문에 커스텀 hooks를 사용합니다.

export function useTodosState() {
  const state = useContext(TodosStateContext);
  if (!state) throw new Error("TodosProvider not found");
  return state;
}

export function useTodosDispatch() {
  const dispatch = useContext(TodosDispatchContext);
  if (!dispatch) throw new Error("TodosProvider not found");
  return dispatch;
}

나중에 컴포넌트에서 사용할 때는 그냥 아래와 같이 사용하면 되겠죠.

import {useTodosDispatch} from "../contexts/TodosContext";

function TodoItem({todo}: TodoItemProps) {
  const dispatch = useTodosDispatch();

  const onToggle = () => {
    dispatch({type: "TOGGLE", id: todo.id});
  };
  const onRemove = () => {
    dispatch({type: "REMOVE", id: todo.id});
  };
(...)

References


https://velog.io/@velopert/typescript-context-api | TypeScript 환경에서 리액트 Context API 제대로 활용하기

profile
세계 최고 수준을 향해 달려가는 개발자입니다.

2개의 댓글

comment-user-thumbnail
2021년 1월 7일

안녕하세요 :) 질문이 하나 있어 댓글 남깁니다! 혹시 context를 정의할 때 undefined를 빼는 방법은 없을까요? undefined가 있으니 context를 쓰는 컴포넌트에서 항상 undefined를 확인하는 전처리를 해주어야해서 불편한 듯 합니다!

1개의 답글