본문 바로가기

React

React / React + Typescript / Context API를 이용한 간단한 TodoList

 

지금까지 리액트는 자바스크립트로만 다뤘다. 타입스크립트를 다루고 싶었지만 문법만 공부하는 것은 재미가 없어 간단한 토이프로젝트들을 진행하면서 타입스크립트를 정리하려고 한다.

 

자바스크립트에서 타입스크립트로 넘어오면서 느낀점

자바스크립트에서 단순히 props로 값을 전달 할 때 발생할 수 있는 잠재적인 오류들이 있다. 어떤 이유로 예상되는 값들이 전달되지 않거나 props의 필드 값들을 미리 적어놓지 않으면 파악하기 힘들다. 이를 해결할 수 있는 방법은 각 필드들에 대한 정보를 기입하고 그 필드들의 타입을 강제하여 발생할 수 있는 오류를 줄이는 것이다.

 

타입스크립트에서는 props에 대해서 타입을 지정한다.

가령 props로 전달하는 객체의 필드들이 name과 age가 있다고 할 때 아래와 같이 interface를 작성할 수 있다.

interface Props {
	name: string;
    age: number;
}

 

타입스크립트에서는 Array에 담기는 요소들의 타입을 지정할 수 있다.

가령 Todos라는 배열의 각 요소에 대한 정의가 필요하다고 할 때 interface를 작성할 수 있다.

interface TodoItem {
	id: number;
    content: string;
    done: boolean;
}

interface Props {
	todos: Array<TodoItem>;
}

 

타입스크립트에서는 메서드들의 타입들을 지정할 수 있다.

상태들과 그 상태들을 수정할 수 있는 메서드들을 동시에 props로 전달한다고 가정해보자. 그렇다면 메서드들에 대해서도 타입을 지정해주어야 한다.

 

interface Props {
	todos: Array<TodoItem>;
    addTodo: (content:string) => void;
    removeTodo: (id: number) => void;
    toggleTodo: (id: number) => void;
}

 

타입스크립트에서는 이벤트 핸들러 함수가 등록한 콜백함수가 받는 매개변수 event에 대해서 타입을 지정해주어야 한다.

const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
	setText(e.target.value);
}

 

onChange 콜백함수를 전달한 React Element는 input이며 발생한 이벤트의 타입이 React.ChangeEvent<HTMLInputElement>임을 확인할 수 있다.

 

type FormEvent = React.FormEvent<HTMLFormElement>
type MouseEvent = React.MouseEvent<HTMLButtonElement>
type ChangeEvent = React.ChangeEvent<HTMLInputElement>

 

위 코드를 통해서 각 Element에 대해서 서로다른 event 객체의 타입을 확인할 수 있다.

 

타입스크립트에서는 useState Hook으로 관리하는 상태에 대한 타입을 지정할 수 있다.

const [count, setCounter] = useState<number>(0);

 

위 코드처럼 useState<type>에 타입을 지정할 수 있다.

 

타입스크립트에서는 Context의 타입을 지정할 수 있다.

interface TodoItem {
   id: number;
   content: string;
   done: boolean;
};

interface TodoContextType {
   todos: Array<TodoItem>;
   addTodo: (content: string) => void;
   removeTodo: (id: number) => void;
   toggleTodo: (id: number) => void;
}

const TodoContext = React.createContext<TodoContextType>({
   todos: [],
   addTodo: () => {},
   removeTodo: () => {},
   toggleTodo: () => {},
});

 

Conext API에 대한 짧은 정리

Conext API는 Props Drilling을 방지하기 위해 사용할 수 있다. 먼저 Conext를 생성한 뒤 그 Conext를 통해서 Provider를 만들고 Provider 컴포넌트에 감싸진 컴포넌트들에 대해 상태를 Props로 전달하지 않고도 전달한다.

 

import React from 'react';

let nextId = 0;

interface TodoItem {
   id: number;
   content: string;
   done: boolean;
};

interface TodoContextType {
   todos: Array<TodoItem>;
   addTodo: (content: string) => void;
   removeTodo: (id: number) => void;
   toggleTodo: (id: number) => void;
}

const TodoContext = React.createContext<TodoContextType>({
   todos: [],
   addTodo: () => {},
   removeTodo: () => {},
   toggleTodo: () => {},
});

interface Props {
   children: React.ReactNode;
}

const TodoProvider = ({ children }: Props) => {
   const [todos, setTodos] = React.useState<Array<TodoItem>>([]);
   const addTodo = (content: string) => {
      setTodos([...todos, {
         id: nextId,
         content,
         done: false,
      }])
      nextId++;
   };

   const removeTodo = (id: number) => {
      setTodos(todos.filter(todo => todo.id !== id));
   }
   
   const toggleTodo = (id: number) => {
      setTodos(todos.map(todo => todo.id === id ? { ...todo, done: !todo.done } : todo));
   }

   return (
      <TodoContext.Provider value={{ todos, addTodo, removeTodo, toggleTodo }}>
         {children}
      </TodoContext.Provider>
   );
}

const useTodo = () => {
   const context = React.useContext(TodoContext);
   if (!context) {
      throw new Error('Cannot find TodoProvider');
   }
   return context;
}

export { useTodo, TodoProvider };

 

위 코드는 Context를 정의한 코드이다. 주목할 부분은 Provider내부에 Children을 전달하여 감싸진 컴포넌트들에게 상태를 전달하는 부분이다

return (
      <TodoContext.Provider value={{ todos, addTodo, removeTodo, toggleTodo }}>
         {children}
      </TodoContext.Provider>
   );

 

또한 custom Hook을 만들어서 상태와 메서드들을 디스트럭처링 문법을 통해 편하게 전달 받을 수 있다.

const useTodo = () => {
   const context = React.useContext(TodoContext);
   if (!context) {
      throw new Error('Cannot find TodoProvider');
   }
   return context;
}