
dispatch 함수를 이용해 구체적인 로직을 숨기고, 보다 선언적으로 변경할 수 있다.
이때 dispatch로 넘기게 되는 object를 action이라고 부른다. (action에는 최소한의 정보만 담아야 한다.)
action에 어떤 값을 담든 상관없지만 일반적으로 type을 넣는게 컨벤션이다.
type은 어떤 동작을 해야 하는지 명시하기 위한 것이다.
기본 형태
dispatch({
// specific to component
type: 'what_happened',
// other fields go here
});
setState를 사용한 경우
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));
}
dispatch를 사용한 경우
function handleAddTask(text) {
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}
function handleChangeTask(task) {
dispatch({
type: 'changed',
task: task,
});
}
function handleDeleteTask(taskId) {
dispatch(
// action object
{
type: 'deleted',
id: taskId,
}
);
}
reducer 함수는 state를 다루는 로직을 담은 함수이다.
argument로 current state와 action object를 받는다.
function yourReducer(state, action) {
// return next state for React to set
}
reducer 함수에서는 state를 argument로 선언하기 때문에 컴포넌트 외부에서 state를 선언할 수 있다. 이러면 indentation level도 낮아지고 가독성을 높일 수 있다.
(추가로 if/else 문으로 써도 되지만 아래와 같이 switch 문으로 작성하는게 컨벤션이다. 가독성 차원에서 더 좋기 때문.)
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);
}
}
}
참조
reducer 함수는 javascript의
reduce메서드의 동작 방식과 동일한 아이디어를 사용한다.
import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
export default function TaskApp() {
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
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,
});
}
return (
<>
<h1>Prague itinerary</h1>
<AddTask onAddTask={handleAddTask} />
<TaskList
tasks={tasks}
onChangeTask={handleChangeTask}
onDeleteTask={handleDeleteTask}
/>
</>
);
}
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);
}
}
}
let nextId = 3;
const initialTasks = [
{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},
];