useReducer

2023-09-17
ReactHooks

Table of Contents

useReducer가 나오게 된 배경

  • 복잡한 React 애플리케이션에서 상태 관리를 더 구조적이고 예측 가능하게 만들고 복잡성을 줄이기 위한 도구로 등장했습니다.

1. 프로젝트 세팅 React + TS + Vite

$ npm init vite@latest React-UseReducer -- --template react-ts
$ cd React-UseReducer
$ npm i

2. useReducer 사용해보기

useReducer의 구조

const [state, dispatch] = useReducer(reducer, initialState, init?);
  • useReducer는 두개의 항목을 배열안에 리턴하는데 첫번째 항목은 값, 두번째 항목은 dispatch 함수를 리턴합니다.

  • useReducer의 첫번째 파라미터로 reducer함수와 두번째 파라미터는 초기화될 값, 그리고 마지막 파라미터로 Lazy Initialization을 optional로 지원합니다.

    function init(initialState){
      return initialState;
    }
    

/src/App.tsx

import { useReducer } from "react";

import "./App.css";

// action의 타입 정의
type Action = {
  type: "Plus" | "Minus" | "Default";
};

// 리듀서 함수
function reducer(state: number, action: Action) {
  console.log("state :: ", state, "action :: ", action);
  switch (action.type) {
    case "Plus":
      return state + 1;
    case "Minus":
      return state - 1;
    case "Default":
			return 0;
    default:
      return state;
  }
}

function App() {
  const [state, dispatch] = useReducer(reducer, 0);

  function plusClickHandler(event: React.MouseEvent<HTMLButtonElement>) {
    event.preventDefault();
    dispatch({ type: "Plus" });
  }
  function minusClickHandler(event: React.MouseEvent<HTMLButtonElement>) {
    event.preventDefault();
    dispatch({ type: "Minus" });
  }
  function defaultClickHandler(event: React.MouseEvent<HTMLButtonElement>) {
    event.preventDefault();
    dispatch({ type: "Default" });
  }

  return (
    <div>
      <button onClick={plusClickHandler}>Plus!</button>
      <button onClick={minusClickHandler}>Minus!</button>
      <button onClick={defaultClickHandler}>Default!</button>
      <div>state :: {state}</div>
    </div>
  );
}

export default App;
  • 버튼을 누를때마다 onClick시 할당한 함수가 실행됩니다.
  • dispatch 함수가 할당된 reducer 함수를 실행시킵니다. reducer함수 에는 dispatch 함수가 보낸 현재 state와 인자로 보낸 객체가 담겨져 있습니다.
// action의 타입 정의
type Action = {
  type: "Plus" | "Minus" | "Default";
};

// 리듀서 함수
function reducer(state: number, action: Action) {
  console.log("state :: ", state, "action :: ", action);
  switch (action.type) {
    case "Plus":
      return state + 1;
    case "Minus":
      return state - 1;
    case "Default":
			return 0;
    default:
      return state;
  }
}
  • 저희가 작성한 reducer 함수에서 action의 type의 값 별로 분기하는 로직을 작성하였습니다. reducer의 일반적인 패턴입니다.
  • 결과론적으로, 현재 할당된 state 값과 같은 타입의 값을 return해야합니다.

업데이트 로직 분리하기(리듀서 함수의 모듈화)

src/reducer/AppReducer.ts

type Action = {
  type: "Plus" | "Minus" | "Default";
};

// 리듀서 함수
function AppReducer(state: number, action: Action) {
  console.log("state :: ", state, "action :: ", action);
  switch (action.type) {
    case "Plus":
      return state + 1;
    case "Minus":
      return state - 1;
    case "Default":
			return 0;
    default:
      return state;
  }
}
export { AppReducer };
  • 간단한 Reducer를 리턴하는 파일을 만들었습니다.

/src/App.tsx

import { useReducer } from "react";
import { AppReducer as reducer } from "./reducer/AppReducer";
import "./App.css";

function App() {
  const [state, dispatch] = useReducer(reducer, 0);

  function plusClickHandler(event: React.MouseEvent<HTMLButtonElement>) {
    event.preventDefault();
    dispatch({ type: "Plus" });
  }
  function minusClickHandler(event: React.MouseEvent<HTMLButtonElement>) {
    event.preventDefault();
    dispatch({ type: "Minus" });
  }
  function defaultClickHandler(event: React.MouseEvent<HTMLButtonElement>) {
    event.preventDefault();
    dispatch({ type: "Default" });
  }

  return (
    <div>
      <button onClick={plusClickHandler}>Plus!</button>
      <button onClick={minusClickHandler}>Minus!</button>
      <button onClick={defaultClickHandler}>Default!</button>
      <div>state :: {state}</div>
    </div>
  );
}

export default App;
  • App.tsx를 다시 작성해보았습니다. 코드가 확실히 적어진 모습입니다.

3. useReducer의 타입

초기화

  • useReducer의 제네릭타입은 Reducer타입을 상속받은 R과, I 타입의 제네릭을 받습니다.
  • reducer: reducer 함수의 타입은 R타입입니다. R타입은 Reducer타입을 상속받고 있습니다.
    • 타입을 보시면 Reducer란 타입은 함수형태이며, prevState에 S 타입과, action에 A타입을 받고 있고, S타입의 값을 리턴하고 있습니다. 요약하면, 이전값과 액션을 받아 현재할당된 State타입을 리턴하는 함수라고 요약할 수 있습니다.
  • initializerArg: I 타입과 ReducerState<R>타입을 모두 만족하는 타입을 받고, ReducerState<R>타입을 할당합니다.
    • ReducerState는 Reducer 타입의 제네릭 R을 할당받으며, R의 prevState가 S타입으로 확장하면 S로 할당하고 아니면 never를 할당합니다.
  • initializer: 파라미터 arg는 I 타입과 ReducerState<R>타입을 모두 만족하는 타입을받고, ReducerState<R>을 리턴하는 함수입니다.

Return Values

  • 배열의 첫 번째 주소에 할당된 ReducerState<R>은 Reducer 타입의 제네릭 R을 할당받으며, R의 prevState가 S타입으로 확장하면 S로 할당하고 아니면 never를 할당합니다.

  • 배열의 두 번째 주소에 할당된 ReducerState<R>은 Reducer 타입의 제네릭 R을 할당받으며, R의 action이 A타입으로 확장하면 A로 할당하고 아니면 never를 할당합니다.