팀 프로젝트를 하면서 useReducer에 대해 알게 되었다. React 공식 문서와 다른 분들의 글을 보면서 공부한 것을 정리해 본다. 정리 과정에서 타입스크립트도 적용해 보았다.
useReducer를 사용하는 이유?
useState는 작성해야 하는 코드의 양이 적어 상태 변경이 매우 간단할 때 읽기 쉽다. 하지만 컴포넌트가 커지고 상태 변경 로직이 복잡해지면 가독성이 떨어진다. 또한 많은 이벤트 핸들러에 여러 상태 변경이 분산되면 가독성도 좋지 않을뿐더러, 상태 변경 로직을 관리하는 것이 어려워진다. 이러한 경우 useReducer를 사용하면 상태 변경 로직을 reducer 내부로 옮겨 이벤트 핸들러와 상태 변경 로직을 완전히 분리하면서, 모든 상태 변경 로직을 한 곳에 통합시킬 수 있다.
useReducer가 성능적으로 useState보다 크게 뛰어난 부분은 없다고 한다. 둘 중 어떤 방법을 선택할지는 성능적인 문제보다는 상태 관리의 복잡성에 초점을 맞춰 정하면 된다고 한다.
useReducer를 사용하여 상태 관리 하는 방법
처음부터 익숙하지 않은 reducer를 구현하려고 하기보다는 익숙한 useState를 통해 상태를 정의하고 HTML 요소와 데이터 바인딩, 이벤트 핸들러 추가 등의 작업을 마친 후에 reducer로 migrate 하는 게 좋을 듯하다. 공식 페이지에서도 useState에서 migrate 하는 과정을 보여줘서 좀 더 수월하게 이해할 수 있었다.
1. Event handler에 dispatch 추가 (action 전달)
function handler(){
dispatch({
type: '발생한 일',
payload: { key: value, ... }
})
}
dispatch에 넘기는 객체를 Action이라고 한다.
Reducer로 상태를 관리하는 것은 직접 상태를 관리하는 것과 다르다. 상태를 설정하여 React에게 "무엇을 해야 하는지" 알려주는 대신, 이벤트 핸들러에서 "Action"을 전달하여 "사용자가 방금 수행한 작업"을 지정한다.
Action 객체는 어떤 형태든 될 수 있다. 관례적으로 '발생한 일'을 묘사하는 단어를 type으로 전달하고, 다른 field로 추가 정보를 전달한다.
2. Reducer 구현
// tasksReducer.ts
export interface Task {
id: number;
text: string;
done: boolean;
}
type Action =
{ type: "added"; task: Task }
| { type: "changed"; task: Task }
| { type: "deleted"; id: number };
export function tasksReducer(tasks: Task[], action: Action):Task[] {
switch (action.type) {
case 'added': {
return [
...tasks,
action.task,
];
}
case 'changed': {
return tasks.map((t) => {
if (t.id === action.task.id) {
return action.task;
} else {
return t;
}
});
}
case 'deleted': {
return tasks.filter((t) => t.id !== action.id);
}
default: {
throw new Error('Unknown action: ' + action.type);
}
}
}
3. 컴포넌트에서 사용하기
- useReducer
reducer 함수와 초기 상태를 인자로 넘겨주면 상태 저장 값과 dispatch 함수를 반환한다.
const [state, dispatch] = userReducer(reducer, initialState);
state는 상황에 맞게 이름을 바꿀 수 있으나 dispatch 함수는 'dispatch'로만 사용하자.
import { useReducer } from 'react';
import AddTask from './AddTask.ts';
import TaskList from './TaskList.ts';
import { Task, tasksReducer } from './tasksReducer.ts';
export default function TaskApp() {
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
function handleAddTask(text: string) {
dispatch({
type: 'added',
task: {id: nextId++, text: text, done: false},
});
}
function handleChangeTask(task: Task) {
dispatch({
type: 'changed',
task: task,
});
}
function handleDeleteTask(taskId: number) {
dispatch({
type: 'deleted',
id: taskId,
});
}
return (
<>
<h1>Prague itinerary</h1>
<AddTask onAddTask={handleAddTask} />
<TaskList
tasks={tasks}
onChangeTask={handleChangeTask}
onDeleteTask={handleDeleteTask}
/>
</>
);
}
let nextId = 3;
const initialTasks:Task = [
{id: 0, text: 'Visit Kafka Museum', done: true},
{id: 1, text: 'Watch a puppet show', done: false},
{id: 2, text: 'Lennon Wall pic', done: false},
];
자주 사용할지는 모르겠지만 종종 사용하면서 '이게 어떤 거였더라?'하지는 않도록 해야겠다.
'Develop > React' 카테고리의 다른 글
[React] 라이브러리 없이 Toast 구현하기 (2) | 2023.11.02 |
---|---|
[React] ios 환경에서 input, textarea 화면 확대 방지하기 (+ 웹 접근성) (0) | 2023.10.13 |
[React] React Router - RouterProvider, createBrowserRouter (0) | 2023.09.11 |
[React] Pagination 구현하기 (1) | 2023.08.14 |
[React] CSS 없이 간단한 별점 기능(Star Rating) 구현하기 (0) | 2023.07.27 |