React Hooks - useReducer()

RumbleBi·2022년 7월 18일
0

React

목록 보기
7/9
post-thumbnail

useReducer()

useReducer 는 언제 사용할까? useState 와 비슷한 기능을 하지만, 여러개의 하위 값들을 포함한 state들을 다루어야 할 때 useReducer를 사용하게 된다면 더욱 간편하게 관리 할 수 있다. useReducer의 작동 원리를 쉽게 이해하기 위해 은행을 예로 들어보자.

Reducer

Reducer 는 state를 업데이트 해주는 역할을 한다. 즉 은행이 고객의 출금요청, 입금요청 들의 요청을 받아 거래내역을 업데이트 해주는 역할이다. 여기서는 고객이 직접 변경하지 않고 은행이 대신 업데이트를 해준다는 의미가 중요하다.

Dispatch

위에서 설명했듯이 고객이 무언가를 은행에 요구하는 행위를 Dispatch 라고 한다. Dispatch 안에 인자로 Action을 넣어서 Reducer에게 전달을 해주는 것이다.

Action

고객의 요구사항의 내용을 Action 이라고 한다. Dispatch를 통해 Action 을 Reducer에 보내고 state를 업데이트 하게 되는 것이다. Action 에는 여러가지가 있을 수 있다. 예금, 출금, 적금통장 개설 등 여러가지 상황을 보낼 수 있다는 것이다.

입금 출금 기능

// App.js

import React, { useReducer, useState } from "react";

// action 타입을 객체 형태로 만들어 놓으면 액션을 변경할 때, 한번에 변경되어 간편하다.
const ACTION_TYPES = {
  deposit: "deposit",
  withdraw: "withdraw",
};

// reducer 함수는 state값과 action을 받는다. 즉 여기서는 money가 들어가게 된다. action은 변경 요구에 대한 내용
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);
  // 첫번째 배열에는 state, 두번째는 Dispatch
  // useReducer의 첫번째 인자는 reducer 함수, 두번째 인자는 state의 초기값
  return (
    <>
      <h3>Reducer 은행</h3>
      <p>잔액 : {money}</p>
      <input
        type="number"
        value={number}
        step="1000"
        onChange={(e) => setNumber(parseInt(e.target.value))}
      />
      {/* dispatch를 버튼에 적용해서 reducer 함수를 호출 */}
      <button
        onClick={() => {
          dispatch({ type: ACTION_TYPES.deposit, payload: number });
        }}
      >
        입금
      </button>
      <button
        onClick={() => {
          dispatch({ type: ACTION_TYPES.withdraw, payload: number });
        }}
      >
        출금
      </button>
    </>
  );
}
export default App;

출석부 기능 응용버전

// App.js
import React, { useReducer, useState } from "react";
import Student from "./Student";

// ACTION 타입 변수를 객체로 관리하여 변수 오타 관리 및 수정이 용이함.
const ACTION_TYPES = {
  add: "add",
  delete: "delete",
  checked: "checked",
};

// 리듀서 함수에서 state, action 인자를 받아서 action 타입에 따라 state 값을 변경
const reducer = (state, action) => {
  switch (action.type) {
    case ACTION_TYPES.add:
      const name = action.payload.name;
      const newStudent = {
        id: Date.now(),
        name,
        isHere: false,
      };
      return {
        count: state.count + 1,
        students: [...state.students, newStudent], // 스프레드 연산자로 이전 값과 새로 추가된 학생의 값을 넣는다.
      };
    case ACTION_TYPES.delete:
      return {
        count: state.count - 1,
        students: state.students.filter(
          (student) => student.id !== action.payload.id
        ), // filter()를 활용하여 삭제 버튼을 클릭한 학생 id와 아닌 학생의 id들을 비교해 새로운 배열을 반환시킴
      };
    case ACTION_TYPES.checked:
      return {
        count: state.count,
        students: state.students.map((student) => {
          if (student.id === action.payload.id) {
            return { ...student, isHere: !student.isHere };
          } // 학생 이름을 클릭한 것과 학생 리스트중에 클릭된 학생의 id 값이 같다면 isHere 값을 변경하여 스타일을 변경시킴
          return student;
        }),
      };
    default:
      return state;
  }
};
// state 초기값
const initialState = {
  count: 0,
  students: [],
};

function App() {
  const [name, setName] = useState("");
  // const [state 값, dispatch로 Action 인자를 받아서 리듀서 함수에 전달] = useReducer(리듀서 함수로 state 변경, state 초기값)
  const [studentsInfo, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      <h3>출석부</h3>
      <p>총 학생 수 : {studentsInfo.count}</p>
      <input
        type="text"
        placeholder="이름을 입력하세요."
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <button
        onClick={() => {
          dispatch({ type: ACTION_TYPES.add, payload: { name } });
        }}
        // dispatch 함수에 타입과 페이로드(액션의 실행에 필요한 임의의 데이터)를 넣어서 리듀서 함수에서 작동하도록 값을 넣어줌
      >
        추가
      </button>
      {studentsInfo.students.map((student) => {
        return (
          <Student
            key={student.id}
            name={student.name}
            dispatch={dispatch}
            id={student.id}
            isHere={student.isHere}
            ACTION_TYPES={ACTION_TYPES}
          />
        );
      })}
    </>
  );
}

export default App;
// Student.js

import React from "react";

const Student = ({ name, dispatch, id, isHere, ACTION_TYPES }) => {
  return (
    <div>
      <span
        style={{
          textDecoration: isHere ? "line-through" : "none",
          color: isHere ? "gray" : "black",
        }}
        onClick={() => {
          dispatch({ type: ACTION_TYPES.checked, payload: { id } });
        }}
      >
        {name}
      </span>
      <button
        onClick={() => {
          dispatch({ type: ACTION_TYPES.delete, payload: { id } });
        }}
      >
        삭제
      </button>
    </div>
  );
};

export default Student;

이러한 예시로 useReducer에 대해 간단히 복습을 해보았다. 상태관리 라이브러리 Redux도 useReducer와 비슷한 형식으로 동작한다. Redux를 사용하기 전에 먼저 useReducer를 충분히 이해한 후에 공부한다면 쉽게 이해할 수 있다고 생각한다.

profile
기억보다는 기록하는 개발자

0개의 댓글