프론트엔드/React

[React] useState? useReducer? useReducer에 대해 파헤쳐보자

s_omi 2024. 1. 30. 11:37

useReducer

React Hooks의 하나로서 useState와 같이 state를 생성하고 관리할 수 있는 hook이다. 

여러 개의 하위 값을 포함하는 복잡한 state를 관리할 때 useState 대신에 useReducer를 쓰면 관리 및 유지보수 하기 좋다.

 

useReducer는 Reducer, Dispatch, Action 이 세 가지로 이루어져 있다. 

 

Reducer는 Dispatch가 보낸 Action의 내용대로 state를 직접 수정하는 주체이고, 

Dispatch는 Reducer에게 state를 수정할 방식(Action)을 보내는 주체이고,

Action은 Dispatch가 보낸 state를 수정할 방식을 담고 있는 메시지이다. 

 

 

사용법

1. useReducer

const [value, dispatch] = useReducer(reducer, 0);

 

useReducer는 useState와 같이 배열을 return 한다. 

return한 배열의 첫 번째 요소에는 새로 만든 state가 들어있고 두 번째 요소에는 useReducer가 만들어 준 dispatch 함수가 들어있다.

 

useReducer는 인자를 두 가지를 받는다.

첫 번째 인자로는 reducer 함수를 받고 두 번째 인자로는 새로 만든 state에 들어갈 초기값을 받는다.

 

2. reducer

dispatch가 호출될 때 reducer 함수가 실행된다.

const reducer = (state, action) => {
  // action을 토대로 state를 변경할 내용
  switch (action.type) {
    case 'plus':
      return state + action.payload;
    case 'minus':
      return state - action.payload;
  }
};

 

reducer 함수는 두 가지의 인자를 받는다. 

첫 번째 인자로는 현재 state를 받고 두 번째 인자로는 action을 받는다. 

 

주로 switch를 통해 action의 type에 따라 코드를 구분해서 작성한다.

 

3. dispatch

<button 
  onClick{() => {
    dispatch({ type: 'plus', payload: num });
  }}
>
  클릭
</button>

 

dispatch의 인자는 action의 내용이 들어가며 객체 타입으로 보낸다.

그리고 보통 객체 타입의 key로 type와 payload를 사용한다. 

 

 

사용 예시

// App.js

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

const ACTION_TYPES = {
  add: 'add-student',
  delete: 'delete-student',
  mark: 'mark-student'
}

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
        ),
      };
    case ACTION_TYPES.mark:
      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;
  }
}

const initStudentsInfo = {
  count: 0,
  students: [],
};

function App() {
  const [name, setName] = useState('');
  const [studentsInfo, dispatch] = useReducer(reducer, initStudentsInfo);

  return (
    <div>
      <h2>출석부</h2>
      <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 } });
        }}  
      >
        추가
      </button>
      {studentsInfo.students.map((student) => {
        return <Student key={student.id} name={student.name} dispatch={dispatch} id={student.id} />;
      })}
    </div>
  );
};
// Student.js

import React from "react";

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

 

다음과 같이 복잡한 state를 관리를 해야할 때 useState 대신 useReducer를 사용한다. 

 

 


출처 : 유튜브 별코딩 (직접 듣기 강추!)