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},
];