[React] useReducer

Main·2023년 7월 17일
0

React

목록 보기
3/31
post-thumbnail

useReducer?

useState 외의 다른 상태관리 hook
컴포넌트의 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있으며, 상태 업데이트 로직을 컴포넌트 바깥에 작성 할 수도 있고, 다른 파일에 불러와서 사용할 수 있다.
복잡한 상태를 다룰 때 유용하며, 유지보수시에도 유용

useReducer 개념

(1) dispath : state 업데이트 요구 사항, 액션을 발생시키는 함수

dispath에는 action의 type과 payload가 들어감
type은 어떤 액션인지 나타내줌, payload는 해당 행동과 관련된 데이터

dispath({type:"deposit", payload:number});

(2) action : state 업데이트 내용, dispath의 인자 reducer에 인자로 전달됨

(3) reduce : state를 업데이트 해주는 역할

useReducer의 첫 번째 인자로는 reducer 함수를 받고, 두 번째 인자로는 상태의 초기 값을 받는다.
상태 값과 dispath 함수를 반환함

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

(4) action type : action type은 코드를 더 깔끔하게 작성하기 위해서 작성하며, 보통 대문자로 작성하는 것이 관례

const ACTION_TYPES = {
  deposit: "deposit",
  withdraw: "withdraw",
};

예시 코드

예시 코드 ( 예금, 송금 )

import { useReducer, useState } from "react";

// reducer : state 업데이트
// dispatch : 요구 사항
// action : 요구 내용

const ACTION_TYPES = {
  deposit: "deposit",
  withdraw: "withdraw",
};

const reducer = (state, action) => {
  // 전달받은 action대로 state 변경
  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);
  // 새로 만든 요소, dispatch함수
  // reducer 함수, 상태 초기값
  // dispatch 실행시 reducer가 호출됨
  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))}
      />
      <button onClick={() => dispatch({ type: "deposit", payload: number })}>
        예금
      </button>
      <button onClick={() => dispatch({ type: "withdraw", payload: number })}>
        송금
      </button>
    </div>
  );
}

export default App;

상태 변경을 두 가지 action를 통해 관리

  • deposit: 현재 금액에서 input에 number를 더해줌
  • withdraw: 현재 금액에서 input에 number를 빼줌
  • 예금 버튼, 송금버튼을 누르면 dispath 함수가 실행되면서 action이 reducer 함수에 전달되고 reducer 함수가 호출
  • 예금 버튼을 누르면 reducer 함수의 switch문에 따라 action.type이 "desposit"인 case가 실행되어 현재 상태값에서 action.payload(현재 예금한 금액)를 더해줌
  • 송금 버튼을 누르면 reducer 함수의 switch문에 따라 action.type이 "withdraw"인 case가 실행되어 현재 상태값에서 action.payload(현재 송금한 금액)를 빼줌

좀 더 복잡한 예시 코드 ( 출석부 )

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

// reducer : state 업데이트
// dispatch : 요구 사항
// action : 요구 내용

const ACTION_TYPES = {
  addStudent: "add-student",
  deleteStudent: "delete-student",
  checkStudent: "check-student",
};

const reducer = (state, action) => {
  switch (action.type) {
    case ACTION_TYPES.addStudent:
      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.deleteStudent:
      return {
        count: state.count - 1,
        students: state.students.filter(
          (student) => student.id !== action.payload.id
        ),
      };
    case ACTION_TYPES.checkStudent:
      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 initalState = {
  count: 0,
  students: [],
};

function App() {
  const [name, setName] = useState("");
  const [studentInfo, dispatch] = useReducer(reducer, initalState);

  return (
    <div>
      <h2>출석부</h2>
      <p>총 학생 수: {studentInfo.count}</p>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <button
        onClick={() =>
          dispatch({ type: ACTION_TYPES.addStudent, payload: { name } })
        }
      >
        추가
      </button>
      {studentInfo.students.map((student) => {
        return (
          <Student
            key={student.id}
            name={student.name}
            dispatch={dispatch}
            id={student.id}
            isHere={student.isHere}
            ACTION_TYPES={ACTION_TYPES}
          />
        );
      })}
    </div>
  );
}

export default App;

Student 컴포넌트

import React from "react";

export default function 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.checkStudent, payload: { id } })}
      >
        {name}
      </span>
      <button
        onClick={() => dispatch({ type: ACTION_TYPES.deleteStudent, payload: { id } })}
      >
        삭제
      </button>
    </div>
  );
}

상태 초기값을 userInfo 객체로 만듬

  • count: 현재 학생의 수
  • students: 학생의 정보 ( id, name, isHere )

상태를 세 가지의 action를 통해 관리

  • add-student: 학생을 생성
  • delete-student: 학생을 삭제
  • check-student: 출석 확인
  • 추가 버튼, 제거 버튼, 이름을 누르면 dispatch 함수가 실행되면서 action이 reducer 함수에 전달되며, reducer 함수가 호출
  • 추가 버튼을 누르면 action으로 "add-student" payload로 name값이 전달 되면서 새로운 학생을 추가
  • 삭제 버튼을 누르면 action으로 "delete-student" payload로 id값이 전달되어 id를 통해 해당 학생을 삭제
  • 학생의 이름을 누르면 action으로 "check-student" payload로 id값이 전달 id를 통해 해당 학생의 isHere 값을 변경
  • isHere 값을 통해 학생 이름의 스타일을 다르게 처리해줌

정리

  • useReducer복잡한 상태관리시 유용하며, 유지보수 또한 용이하다.
  • 간단한 상태관리를 할 때는 useState 사용이 더 좋다.

참고 사이트

React Hooks에 취한다 - useReducer 확실히 정리해드려요 | 리액트 훅스 시리즈

profile
함께 개선하는 개발자

0개의 댓글