[React] useReducer
본문 바로가기
프레임워크/React

[React] useReducer

by 은돌1113 2022. 3. 1.

컴포넌트의 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;


 

'프레임워크 > React' 카테고리의 다른 글

[React] useEffect vs useLayoutEffect  (0) 2022.10.21
[React] Side Effect(useEffect)  (0) 2022.06.21
[React] useCallback  (0) 2022.02.18
[React] useMemo  (0) 2022.01.31
[React] useEffect  (0) 2022.01.29

댓글