useReducer는 reducer를 사용할 수 있도록 하는 리액트 내장 훅이다.
현재 스피치의 스크립트를 편집하고 재생하는 컴포넌트에 너무 많은 상태들이 useState로 관리되고 있어서, 업데이트 로직이 비슷한 상태들을 reducer로 묶어보려고 한다. 그래서 우선 useState로 관리하던 상태들을 useReducer로 리팩터링하는 과정에 집중해서 알아볼 것이다.
상태를 업데이트 하는 로직을 하나의 함수로 컴포넌트와 분리할 수 있는데, 이것을 reducer라고 한다. reducer를 다른 파일로 분리해 사용할 수도 있으며 코드가 복잡해 질 수록 유용하다.
useReducer는 reducer function과 initial state 두 개의 인자를 갖고, stateful value와 dispatch function을 반환한다.
const [state, dispatch] = useReducer(reducer, initialArg, init?)
useState -> useReducer 리팩터링
1. setting state → dispatching actions
- setting state: “무엇을 할 지” 알려줌
- dispatch actions: “사용자가 무엇을 했는지” 알려줌
// setting state
function handleAddTask(text) {
setTasks([
...tasks,
{
id: nextId++,
text: text,
done: false,
},
]);
}
function handleChangeTask(task) {
setTasks(
tasks.map((t) => {
if (t.id === task.id) {
return task;
} else {
return t;
}
})
);
}
function handleDeleteTask(taskId) {
setTasks(tasks.filter((t) => t.id !== taskId));
}
useState를 이용해 코드를 작성하면 위와 같이 이벤트 핸들러 안에서 직접 setState 를 통해 상태를 업데이트 한다. 위의 코드에서 상태를 set하는 로직을 제거해야 하는데, 대신
- handleAddTask(text): 사용자가 “Add”를 눌렀을 때 호출되도록
- handleChangeTask(task): 사용자가 “Change”를 눌렀을 때 호출되도록
- handleDeleteTask(task): 사용자가 “Delete”를 눌렀을 때 호출되도록
남겨놓는다.
// dispatch actions
function handleAddTask(text) {
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}
function handleChangeTask(task) {
dispatch({
type: 'changed',
task: task,
});
}
function handleDeleteTask(taskId) {
dispatch({
type: 'deleted',
id: taskId,
});
}
이렇게 { action }을 dispatch에 넘겨주도록 하는 것이다.
convention: 어떤 일이 일어났는지 설명하는 string인 “type”을 넘겨주는게 일반적
2. reducer 함수 작성
상태를 업데이트하는 로직을 작성한다. 앞서 말했듯 reducer는 current state와 action object 두 개의 인자를 갖는다. 그리고 reducer에서 다음 상태를 반환하면 리액트가 상태를 업데이트 한다.
function tasksReducer(tasks, action) {
switch (action.type) {
case 'added': {
return [
...tasks,
{
id: action.id,
text: action.text,
done: false,
},
];
}
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 Error('Unknown action: ' + action.type);
}
}
}
convention: if/else문 대신 switch문을 사용하는 것이 일반적
위와 같이 상태를 인자로 전달하기 때문에 reducer를 컴포넌트 밖에서 선언할 수 있는 것이다.
3. 컴포넌트에서 reducer 사용
import { useReducer } from 'react';
// const [tasks, setTasks] = useState(initialTasks);
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
useState vs useReducer
useState | useReducer | |
code size | - 미리 작성해야 하는 코드는 더 적음 | - reducer 함수와 dispatch action을 모두 작성해야 함 - 많은 이벤트 핸들러들이 상태를 비슷한 로직으로 변경할 경우 코드 양을 줄일 수 있음 |
readability | - 상태 업데이트가 간단할 경우 가독성 좋음 | - 상태 업데이트 로직이 복잡할 때 로직과 발생한 일을 분리할 수 있게 해줌 |
'React' 카테고리의 다른 글
useReducer로 useState 리팩터링 하기 (2) (0) | 2023.09.15 |
---|---|
useReducer로 useState 리팩터링 하기 (1) (0) | 2023.09.15 |
useEffect로 state 변경 바로 감지하기 (4) | 2023.08.08 |
State 작동 방식 (0) | 2023.08.08 |
styled component에 css animation 적용 (재생되는 텍스트 만들기) (0) | 2023.07.18 |