[React] useReducer
컴포넌트의 state를 생성하고 관리하기 위해서 지금까지 useState Hook을 사용 해왔었는 데요.
React Hook에서는 상태 관리를 위한 Hook으로 useReducer도 제공하고 있습니다!
useReducer는 useState처럼 state를 생성하고 관리 할 수 있게 도와주는 도구입니다.
여러 개의 하위 값을 포함하는 복잡한 state를 다룰 경우 useReducer를 사용하면
훨씬 더 깔끔하게 코드를 작성 할 수 있습니다. 물론 유지보수에도 좋습니다!
useReducer는 Reducer, Dispatch, Action 이 세가지로 이루어져 있습니다.
1️⃣ Reducer
2️⃣ Dispatch
3️⃣ Action
한가지 상황을 예로 들어 설명 하겠습니다.
철수라는 아이가 계좌에서 만원을 출금하기 위해 은행에 방문 했습니다.
은행에서 철수 계좌의 있는 만원을 출금 할 경우 기록이 남습니다. 이때 거래 내역을 state라고 하겠습니다.
철수는 거래 내역이라는 state를 직접 수정하지 않고 은행이 철수의 요구대로 대신 state를 수정 해줍니다.
이때 은행은 Reducer라고 할 수 있습니다. Reducer는 state를 업데이트 해주는 역할을 합니다.
컴포넌트의 state를 변경하고 싶을 때는 Reducer에게 요구를 보내야 합니다.
마치 철수가 은행에 출금을 요청한 것처럼요
여기서 요구라는 행위는 Dispatch라고 할 수 있고,
"만원을 출금 해주세요"라는 요구사항은 Action이라고 할 수 있습니다.
여기서 요구사항에 따라 다른 Action을 요구(Dispatch) 할 수 있습니다.
(ex 예금 Action, 출금 Action, 계좌 생성 Action 등등)
Redux 라이브러리를 React Hook으로 사용 할 수 있는 느낌???
reducer의 가장 좋은 점은 전달 받은 action 대로만 state를 업데이트 해준다는 점입니다.
우리가 예상한 대로만 동작하기 때문에 실수를 어느정도 줄여 줄 수 있습니다.
실습 1️⃣
// ##### App.js #####
import React, { useState, useReducer } from "react";
// reducer : state를 업데이트 해주는 역할 (은행)
// dispatch : state 업데이트를 위한 요구
// action : 요구의 내용
const reducer = (state, action) => {
// 첫번째 인자 : 현재 state, 두번째 인자 : action
console.log("reducer가 일을 합니다.", state, action);
switch (action.type) {
case "deposit":
return state + action.payload;
case "withdraw":
return state - action.payload;
default:
return state;
}
};
function App() {
const [number, setNumber] = useState(0);
const [money, dispatch] = useReducer(reducer, 0);
// 첫번째 요소 : 새로 만들어진 state
// 두번째 요소 : useReducer가 만들어 준 dispatch 함수
// 첫번째 인자 : 우리가 만들어 준 reducer
// 두번째 인자 : 초기값
return (
<div>
<h2>useReducer 은행에 오신 것을 환영합니다.</h2>
<p>잔고: {money}원</p>
<input
type="number"
value={number}
onChange={(e) => setNumber(parseInt(e.target.value))}
step="1000"
/>
<button
onClick={() => {
dispatch({ type: "deposit", payload: number });
}}
>
예금
</button>
<button
onClick={() => {
dispatch({ type: "withdraw", payload: number });
}}
>
출금
</button>
</div>
);
}
export default App;
위 코드처럼 각각 type을 사용하는 방법이 있고
// ##### App.js #####
import React, { useState, useReducer } from "react";
const ACTION_TYPES = {
deposit: "deposit",
withdraw: "withdraw",
};
const reducer = (state, action) => {
console.log("reducer가 일을 합니다.", state, action);
switch (action.type) {
case ACTION_TYPES.deposit:
return state + action.payload;
case ACTION_TYPES.withdraw:
return state - action.payload;
default:
return state;
}
};
function App() {
const [number, setNumber] = useState(0);
const [money, dispatch] = useReducer(reducer, 0);
return (
<div>
<h2>useReducer 은행에 오신 것을 환영합니다.</h2>
<p>잔고: {money}원</p>
<input
type="number"
value={number}
onChange={(e) => setNumber(parseInt(e.target.value))}
step="1000"
/>
<button
onClick={() => {
dispatch({ type: ACTION_TYPES.deposit, payload: number });
}}
>
예금
</button>
<button
onClick={() => {
dispatch({ type: ACTION_TYPES.withdraw, payload: number });
}}
>
출금
</button>
</div>
);
}
export default App;
위 코드처럼 ACTION_TYPES라는 객체를 생성하여 공통으로 관리 해주는 방법도 있습니다.
실습 2️⃣
// ##### App.js #####
import React, { useState, useReducer } from "react";
import Student from "../src/Student";
const initialState = {
count: 1,
students: [{ id: Date.now(), name: "saebom", isHere: false }],
};
const reducer = (state, action) => {
switch (action.type) {
case "add-student":
const name = action.payload.name;
const newStudent = {
id: Date.now(),
name,
isHere: false,
};
return {
count: state.count + 1,
students: [...state.students, newStudent],
};
case "delete-student":
const newStudents = state.students.filter((student) => {
if (student.id !== action.payload.id) {
return student;
}
});
return {
count: state.count - 1,
students: [...newStudents],
};
case "mark-student":
return {
count: state.count,
students: state.students.map((student) => {
if (student.id === action.payload.id) {
return { ...student, isHere: !student.isHere };
}
return student;
}),
};
default:
return state;
}
};
function App() {
const [name, setName] = useState("");
const [studentInfo, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h1>출석부</h1>
<p>총 학생 수 : {studentInfo.count}</p>
<input
type="text"
placeholder="이름을 입력 해주세요"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<button
onClick={() => {
dispatch({ type: "add-student", payload: { name } });
}}
>
추가
</button>
{studentInfo.students.map((student) => {
return (
<Student
dispatch={dispatch}
key={student.id}
id={student.id}
name={student.name}
isHere={student.isHere}
/>
);
})}
</div>
);
}
export default App;
// ##### Student.js #####
import React from "react";
const Student = ({ id, name, dispatch, isHere }) => {
return (
<div>
<span
style={{
textDecoration: isHere ? "line-through" : "none",
color: isHere ? "gray" : "black",
}}
onClick={() => {
dispatch({ type: "mark-student", payload: { id } });
}}
>
{name}
</span>
<button
onClick={() => {
dispatch({ type: "delete-student", payload: { id } });
}}
>
삭제
</button>
</div>
);
};
export default Student;