일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- react-router-dom
- java
- @media
- 자바
- 변수
- max-width
- node.js
- react
- node
- Servlet
- 그럼에도 불구하고
- frontend
- git
- 자바문제풀이
- github
- JavaScript
- HTML
- cleancode
- coding
- 그럼에도불구하고
- 코딩테스트
- TypeScript
- webpack
- JS
- media query
- 프론트엔드
- CSS
- 코드업
- redux
- 반응형 페이지
- Today
- Total
그럼에도 불구하고
[React with TS] useReducer를 이용한 todoList 만들기 ( with TypeScript ) 본문
[React with TS] useReducer를 이용한 todoList 만들기 ( with TypeScript )
zenghyun 2023. 7. 3. 13:15오늘은 useReducer를 이용한 todoList를 만들어보겠습니다.
목차
[ useReducer ]
useReducer 훅을 사용하는 방법은 다음과 같습니다.
// state : 상태
// dispatch : 상태를 변경하는 메서드
// reducer : 새로운 상태를 리턴하는 리듀서 함수
/// initialState : 초기 상태로 지정할 객체
const [state, dispatch] = useReducer(reducer, initialState);
useReducer 훅에 리듀서 함수와 초기 상태를 인자로 전달하여 호출하면 상태와 상태 변경을 위해 메시지를 전달할 수 있는 dispatch 함수가 리턴됩니다. dispatch 함수가 미리 정의한 형식의 메시지를 전달하면 상태를 변경하도록 리듀서 함수를 작성해야 합니다.
이제 간단한 할 일 목록 (TodoList) 예제를 작성하면서 사용 방법을 구체적으로 익히도록 하겠습니다.
📌 TodoReducer.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | import { produce } from "immer"; export type TodoItemType = { id: number; todo: string; }; export const TODO_ACTION = { ADD: "addTodo" as const, DELETE: "deleteTodo" as const, }; export const TodoActionCreator = { addTodo: (todo: string) => ({ type: TODO_ACTION.ADD, payload: { todo: todo }, }), deleteTodo: (id: number) => ({ type: TODO_ACTION.DELETE, payload: { id: id }, }), }; export type TodoActionType = | ReturnType<typeof TodoActionCreator.addTodo> | ReturnType<typeof TodoActionCreator.deleteTodo>; export const TodoReducer = (state: Array<TodoItemType>, action: TodoActionType) => { switch(action.type) { case TODO_ACTION.ADD : return produce(state, (draft: Array<TodoItemType>) => { draft.push({id: new Date().getTime(), todo: action.payload.todo}); }); case TODO_ACTION.DELETE : // eslint-disable-next-line no-case-declarations const index = state.findIndex(item => item.id === action.payload.id); return produce(state, (draft: Array<TodoItemType>)=> { draft.splice(index, 1); }); } }; | cs |
우선, 상태를 초기화하고 dispatch(action)로 상태를 변경하는 작업을 정적 타입으로 수행하려면 타입을 정의해야 합니다.
TodoItemType과 같이 기본적인 상태를 정해주겠습니다.
export type TodoItemType = {
id: number;
todo: string;
};
그리고 TodoReducer 함수를 살펴보면 dispatch(action) 함수를 호출하여 전달되는 action 객체는 어떤 작업을 수행할지(type)와 작업에 필요한 인자(payload)를 포함해야 합니다. 그렇기 때문에 미리 action 객체의 형식을 지정하기 위해 TodoActionCreator 객체와 이 객체의 메서드 리턴값을 이용하여 생성한 TodoActionType를 정의했습니다.
action 객체는 TodoActionType 형식의 객체가 되도록 작성했습니다.
export const TODO_ACTION = {
ADD: "addTodo" as const,
DELETE: "deleteTodo" as const,
};
export const TodoActionCreator = {
addTodo: (todo: string) => ({
type: TODO_ACTION.ADD,
payload: { todo: todo },
}),
deleteTodo: (id: number) => ({
type: TODO_ACTION.DELETE,
payload: { id: id },
}),
};
export type TodoActionType =
| ReturnType<typeof TodoActionCreator.addTodo>
| ReturnType<typeof TodoActionCreator.deleteTodo>;
TodoActionType에서 type 필드는 string이 아니라 addTodo, deleteTodo와 같이 상수로써 사용해야 합니다.
type이 addTodo일 때는 payload가 { todo : string } 타입이고, type이 deleteTodo 일 때는 payload가 { id : number } 형식이어야 하기 때문입니다.
따라서 TODO_ACTION을 작성할 때 as const를 추가하여 상수로 정의합니다.
TodoActionType에서 TodoActionCreator의 각 메서드가 리턴하는 값들을 이용해야 하므로 ReturnType이라는 유틸리티 타입을 이용해 리턴값의 타입을 추출해서 사용합니다.
그다음은 작성된 타입과 액션 생성자 메서드들을 이용해 TodoReducer를 작성합니다. TodoReducer에서는 첫 번째 인자(state)가 두 번째 인자(action)를 이용해 상태를 연산하여 새로운 상태를 리턴해야 합니다. 이때 action의 type에 따라 서로 다른 작업을 수행해야 하므로 switch문으로 분기해서 처리합니다. ( if 문을 사용해도 무방 합니다 ) 그리고 기존 상태는 불변성을 가져야 하므로 immer 라이브러리를 이용해 새로운 상태를 생성하여 리턴합니다.
📌 App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | import { useState, useReducer } from "react"; import { TodoItemType, TodoActionCreator, TodoReducer } from "./TodoReducer"; const idNow = new Date().getTime(); const initialTodoList: Array<TodoItemType> = [ { id: idNow, todo: "React" }, { id: idNow + 1, todo: "TypeScript" }, { id: idNow + 2, todo: "NextJS" }, ]; const App = () => { const [todoList, dispatchTodoList] = useReducer(TodoReducer, initialTodoList); const [todo, setTodo] = useState<string>(""); const addTodo = () => { dispatchTodoList(TodoActionCreator.addTodo(todo)); setTodo(""); }; const deleteTodo = (id: number) => { dispatchTodoList(TodoActionCreator.deleteTodo(id)); }; return ( <div style={{ padding: "20px" }}> <input type="text" value={todo} onChange={(e) => setTodo(e.target.value)} /> <button onClick={addTodo}>할 일 추가</button> <ul> {todoList.map((item) => ( <li key={item.id}> {item.todo} <button onClick={() => deleteTodo(item.id)}>삭제</button> </li> ))} </ul> </div> ); }; export default App; | cs |
※ 할 일 추가 전
※ 할 일 추가 후
'React > React basics' 카테고리의 다른 글
[React] 레이지 로딩이란? (0) | 2023.07.08 |
---|---|
[React with TS] Context API를 이용한 todoList 만들기 (0) | 2023.07.05 |
[React with TS] SyntheticEvent를 통한 React 이벤트 적용 (with TypeScript) (0) | 2023.06.29 |
[React with TS] PropTypes란? (0) | 2023.06.28 |
[React] dangerouslySetInnerHTML이란? (0) | 2023.06.28 |