Front-end/React

[React] useReducer: 상태 관리의 새로운 차원! 🎛️

xeunnie 2024. 12. 10. 01:00
728x90
반응형

React의 톺아보기! 🎛️

React에서 상태 관리를 할 때, 가장 먼저 떠오르는 도구는 useState죠. 하지만 상태가 복잡하거나 여러 상태가 서로 얽혀 있을 때는 useReducer가 훨씬 깔끔하고 유지보수가 쉬운 선택이 될 수 있습니다. 이번에는 useReducer의 기본부터 실무 활용까지, 모든 것을 다뤄볼게요!


useReducer란 무엇인가요? 🤔

useReducer는 리듀서 패턴을 기반으로 한 React의 상태 관리 Hook입니다. 리듀서 패턴은 reducer 함수와 action을 사용해 상태를 업데이트하는 방식으로, Redux와 유사한 구조를 제공합니다.
 

언제 useReducer를 사용할까요?

  • 상태가 여러 개의 값으로 구성되어 복잡한 경우
  • 상태 업데이트 로직이 명확히 구조화되어야 할 때
  • 상태 관리와 관련된 액션이 다양할 때

useReducer의 기본 문법 🛠️

문법 구조

const [state, dispatch] = useReducer(reducer, initialState);

 

  • reducer: 상태 업데이트 로직을 정의한 함수
  • initialState: 상태의 초기값
  • state: 현재 상태 값
  • dispatch: 상태를 업데이트하기 위해 action을 보내는 함수

useReducer 기본 예제 📘

카운터 예제: 가장 기본적인 useReducer 사용법

import React, { useReducer } from "react";

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    case "reset":
      return { count: 0 };
    default:
      throw new Error("Unknown action type");
  }
}

function Counter() {
  const initialState = { count: 0 };
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "reset" })}>Reset</button>
    </div>
  );
}

export default Counter;

 

코드 해석

  1. reducer 함수:
    • state: 현재 상태 값
    • action: 상태를 업데이트하는 명령(객체 형태)
    • switch문으로 action.type에 따라 상태 업데이트 로직을 정의
  2. dispatch 호출:
    • dispatchaction 객체를 전달받아 reducer를 실행시킵니다.

useReducer의 장점 🌟

  • 명확한 상태 관리: 상태 업데이트 로직이 하나의 함수에 모여 있어 가독성과 유지보수성이 뛰어남
  • 복잡한 상태 처리: 여러 상태가 얽혀 있을 때도 간단하게 관리 가능
  • 액션 기반 업데이트: 어떤 상태 변화가 어떤 액션에 의해 발생했는지 명확히 알 수 있음

useReducer 실무 활용 예제 💼

1. 복잡한 폼 상태 관리

import React, { useReducer } from "react";

function formReducer(state, action) {
  switch (action.type) {
    case "SET_FIELD":
      return { ...state, [action.field]: action.value };
    case "RESET":
      return { username: "", email: "" };
    default:
      throw new Error("Unknown action type");
  }
}

function Form() {
  const initialState = { username: "", email: "" };
  const [state, dispatch] = useReducer(formReducer, initialState);
  const handleChange = (e) => {
    dispatch({ type: "SET_FIELD", field: e.target.name, value: e.target.value });
  };
  const handleReset = () => {
    dispatch({ type: "RESET" });
  };
  return (
    <div>
      <input
        name="username"
        value={state.username}
        onChange={handleChange}
        placeholder="Username"
      />
      <input
        name="email"
        value={state.email}
        onChange={handleChange}
        placeholder="Email"
      />
      <button onClick={handleReset}>Reset</button>
      <p>Username: {state.username}</p>
      <p>Email: {state.email}</p>
    </div>
  );
}

export default Form;

 
핵심 포인트

  1. 여러 상태(username, email)를 하나의 리듀서에서 관리
  2. 상태를 업데이트하는 로직이 한 곳에 모여 있어 코드가 깔끔

 

2. 비동기 작업 처리

useReducer와 useEffect를 결합하면 비동기 상태 관리도 쉽게 처리할 수 있어요.

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

function dataReducer(state, action) {
  switch (action.type) {
    case "FETCH_INIT":
      return { ...state, isLoading: true, isError: false };
    case "FETCH_SUCCESS":
      return { ...state, isLoading: false, data: action.payload };
    case "FETCH_FAILURE":
      return { ...state, isLoading: false, isError: true };
    default:
      throw new Error("Unknown action type");
  }
}

function DataFetcher({ url }) {
  const initialState = {
    isLoading: false,
    isError: false,
    data: [],
  };

  const [state, dispatch] = useReducer(dataReducer, initialState);
  useEffect(() => {
    const fetchData = async () => {
      dispatch({ type: "FETCH_INIT" });
      try {
        const response = await fetch(url);
        const result = await response.json();
        dispatch({ type: "FETCH_SUCCESS", payload: result });
      } catch (error) {
        dispatch({ type: "FETCH_FAILURE" });
      }
    };
    fetchData();
  }, [url]);

  return (
    <div>
      {state.isError && <p>Error fetching data.</p>}
      {state.isLoading ? (
        <p>Loading...</p>
      ) : (
        <ul>
          {state.data.map((item) => (
            <li key={item.id}>{item.title}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default DataFetcher;

useReducer vs useState: 언제 무엇을 선택할까? ⚖️

기준 useState useReducer
복잡도 단순한 상태 관리에 적합 복잡한 상태 관리와 여러 액션 처리에 적합
상태 변경 로직 컴포넌트 내부에서 직접 상태를 업데이트 리듀서를 통해 상태 변경 로직을 분리
코드 구조 간단하고 빠르게 구현 가능 코드가 조금 더 길어지지만 명확한 구조 제공
가독성 간단한 상태에는 가독성 좋음 복잡한 상태에서는 가독성 뛰어남

주의사항 🧐

  1. 리듀서 함수는 순수 함수로 작성해야 합니다.
    • 같은 입력 → 항상 같은 출력
    • 부수 효과(side effects) 발생 금지
  2. 초기 상태에 유의하세요.
    • initialState를 명확히 정의하지 않으면 상태 초기화에 혼란이 생길 수 있음
  3. 액션 타입 관리
    • 액션 타입은 문자열이므로 오타나 실수를 방지하려면 상수로 관리하는 것이 좋음
const ACTIONS = {
  INCREMENT: "increment",
  DECREMENT: "decrement",
};

결론: useReducer로 상태를 정복하자! 🏆

React의 useReducer는 복잡한 상태를 관리하고 액션 기반으로 로직을 구조화하는 데 최적화된 도구입니다. 적재적소에 사용한다면, 코드의 가독성과 유지보수성이 크게 향상될 거예요.
 

🌷전설의 개발자가 되어봅시당! 🌷

728x90
반응형