ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • useReducer의 장점 알아보기 (vs useState)
    React 2023. 7. 15. 10:40

    useReducer의 존재는 알고있었고, useState를 대체한다는 점 역시 알고있었다.

    redux의 dispatch나 reducer 개념이 useReducer로부터 비롯되었다는 것도 알았지만-

    실제로 무슨 장점이 있기에 useState대신 useReducer를 쓰는가에 대해서 진지하게 생각해본적이 없었다는 것을 알게 되었다.

    따라서, 이번 기회에 useReducer의 장점에 대해서 기록해보고자 하였다.

     

    🐥 닉네임과 보유한 캐시를 상태로 가지는 컴포넌트를 가정하고, useState와 useReducer를 비교해보는 예제를 만들었다.

     

    일단, useState를 사용한 예제이다.

    import { useState } from "react";
    
    export default function App() {
      const [nickname, setNickname] = useState<string>("");
      const [balance, setBalance] = useState<number>(0);
      const changeNickname = (name: string) => {
        setNickname(name);
      };
      const changeBalance = (money: number) => {
        setBalance(money);
      };
      return (
        <div>
          <h2>
            {nickname}님의 보유 캐시는 {balance}입니다.
          </h2>
          <button onClick={() => changeNickname("Hahaha")}>닉네임 변경!</button>
          <button onClick={() => changeBalance(5000)}>잔액 충전!</button>
        </div>
      );
    }

    익숙한 코드이고, 딱히 문제점은 보이지 않는다.

    각 버튼에는 닉네임과 잔액을 변경할 수 있는 함수가 연결되어있다.

    함수에 닉네임과 금액을 전달하여 상태를 변경할 수 있다.

     

    그렇다면, 이번엔 useReducer를 통해서 동일한 기능을 구현한 컴포넌트를 작성해보자. 😎

    import { useReducer } from "react";
    
    interface ReducerState {
      nickname: string;
      balance: number;
    }
    
    type ReducerType = "SET_NICKNAME" | "SET_BALANCE";
    
    interface ReducerAction {
      type: ReducerType;
      payload: number | string;
    }
    
    const initialState = {
      nickname: "",
      balance: 0
    };
    
    const reducer = (state: ReducerState, action: ReducerAction): ReducerState => {
      switch (action.type) {
        case "SET_NICKNAME": {
          if (typeof action.payload === "string") {
            return { ...state, nickname: action.payload };
          } else {
            return state;
          }
        }
        case "SET_BALANCE": {
          if (typeof action.payload === "number") {
            return { ...state, balance: action.payload };
          } else {
            return state;
          }
        }
        default:
          return state;
      }
    };
    
    export default function App() {
      const [state, dispatch] = useReducer(reducer, initialState);
      const handleNickname = () => {
        dispatch({ type: "SET_NICKNAME", payload: "Hihihi" });
      };
      const handleBalance = () => {
        dispatch({ type: "SET_BALANCE", payload: 14700 });
      };
      return (
        <div>
          <h2>
            {state.nickname}님의 보유 캐시는 {state.balance}입니다.
          </h2>
          <button onClick={handleNickname}>닉네임 변경!</button>
          <button onClick={handleBalance}>잔액 충전!</button>
        </div>
      );
    }

    일단 코드가 길어졌다. (redux 코드와 거의 유사하다)

    그렇다면, 이 길어진 코드에 무슨 장점이 있는걸까?

     

    🍉 역시 가장 눈에띄는 것은 reducer다.

    • reducer 함수 내부에 action으로 미리 어떻게 상태가 변화될지 정해놓을 수 있다.
    • 따라서 상태 관리 로직을 명확하게 분리하고, 액션 타입을 미리 정의하여 상태 업데이트를  처리할 수 있다.
    • 기존의 useState의 change함수나 handler함수는 모두 각각 정의되는 반면, useReducer를 사용하면 dispatch에 적절한 action 객체를 전달함으로써, 여러 상태의 변화를 하나의 reducer 함수로 관리할 수 있다.
    • 만약, 다수의 하위 컴포넌트에 상태를 전달해야하는 상위 컴포넌트에 useReducer를 사용하면, 좀 더 중앙 집중화된 상태관리 로직을 작성할 수 있을 것이다.
    • 반면, useState를 사용하면 상태 업데이트 로직은 각각 컴포넌트에 위치하여 상태의 구조와 변화를 파악하는 일을 방해하게 될것이다.

     

     

    const [userData, setUserData] = useState({nickname: "", balance: 0});
    
    const handleUserData = (newState) => {
      setUserData(prev => {
        ...prev,
        ...newState
      });
    }
    
    ...
    
    return (
        <button onClick={() => handleUserData({balance: 2000})}>잔액 충전!</button>
    )

    위와 같이 상태를 객체로 만들어 관리하면, 좀 더 중앙 집중된 상태를 사용할 수 있지만, reducer 함수에 미리 상태 변화의 가능성들을 명시해두는 편이 에러를 발견하고 예외를 처리하는데 있어서 용이하다.

    action 객체의 type으로 상태 변화의 케이스들이 관리되기 때문에, 유지보수하는 입장에서도 문제가 생긴 부분을 빠르게 파악할 수 있는 장점도 있다.

    따라서, 위와 같이 객체로 상태를 관리할 경우에는 useReducer를 사용하는 편이 좀 더 관리적인 측면에서 좋은 것 같다. (초기 작성 코드는 좀 늘어날지도 모르겠지만...)

     

    또한, map을 사용하는데 있어서 이점도 존재한다.

    기존의 useState에서의 값 변경 함수는 각 상태마다 별개로 존재했지만, useReducer에서는 일관되게 dispatch를 통해서 값을 변경하기 때문에, map을 사용할 때와 같이 일관된 코딩이 필요한 시점에 더 유용하게 사용될 수 있다.

     

    👏 또한, useReducer를 사용하면 상태 관련 로직(reducer)이 컴포넌트와 분리되어 외부에서 이를 관리할 수 있고, useState보다 더 세밀한 상태 관리가 가능하기에 불필요한 렌더링을 방지할 수 있다.

     

    ⛈ useReducer 공식문서

    https://react.dev/reference/react/useReducer

     

    useReducer – React

    The library for web and native user interfaces

    react.dev

     

     

Designed by Tistory.