개발/🟦 React

🟦[React] useReducer

비니_ 2025. 6. 10. 09:17
728x90

 

간단한 상태는 useState 사용

복잡한 경우 useReducer 사용

ex) const initialState = { count: 0, loading: false, error: null };

 

import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
    switch (action.type) {
        case 'INCREMENT':
            return { count: state.count + 1 };
        case 'DECREMENT':
            return { count: state.count - 1 };
        default:
            return state;
    }
}

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

    return (
        <div>
            <h2>Count: {state.count}</h2>
            <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
            <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
        </div>
    );
}

export default Counter;

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

 

  • state: 현재 상태값을 담고 있는 객체
  • dispatch: 액션을 보내는 함수
  • reducer: 액션에 따라 상태를 어떻게 바꿀지 정의한 함수
  • initialState: 처음 상태
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>

 

 

dispatch({ type: 'INCREMENT' })를 호출하면:

  1. reducer(state, { type: 'INCREMENT' }) 실행됨
  2. 새로운 상태 { count: state.count + 1 } 반환
  3. 그 상태가 state에 저장되고 리렌더링됨

 

 

✅ 흐름 정리 (비유)

1. 초기상태 count: 0 으로 시작
2. 액션 발생 dispatch({ type: 'INCREMENT' })
3. reducer 동작 상태를 +1 증가시켜 새 객체 반환
4. 컴포넌트 리렌더링 state.count 값이 갱신됨

 

 

User 클릭 → dispatch() 호출 → reducer 실행 → 새로운 state 반환 → 컴포넌트 다시 렌더링

 

 

복잡한 경우 예시

import React, { useReducer } from 'react';

const initialState = {
  count: 0,
  loading: false,
  error: null,
};

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    case 'LOADING_START':
      return { ...state, loading: true, error: null };
    case 'LOADING_SUCCESS':
      return { ...state, loading: false };
    case 'LOADING_ERROR':
      return { ...state, loading: false, error: action.payload };
    default:
      return state;
  }
}

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

  // 버튼 클릭 시 모의 비동기 작업 예시
  const simulateLoading = () => {
    dispatch({ type: 'LOADING_START' });

    setTimeout(() => {
      // 성공 시
      dispatch({ type: 'LOADING_SUCCESS' });

      // 실패 시 (주석 해제하고 테스트 가능)
      // dispatch({ type: 'LOADING_ERROR', payload: '오류 발생!' });
    }, 1000);
  };

  return (
    <div>
      <h2>Count: {state.count}</h2>
      {state.loading && <p>로딩 중...</p>}
      {state.error && <p style={{ color: 'red' }}>에러: {state.error}</p>}
      <button onClick={() => dispatch({ type: 'DECREMENT' })} disabled={state.loading}>-</button>
      <button onClick={() => dispatch({ type: 'INCREMENT' })} disabled={state.loading}>+</button>
      <button onClick={simulateLoading} disabled={state.loading}>비동기 작업 시작</button>
    </div>
  );
}

export default Counter;

이 예시 핵심

  • count뿐 아니라 loading, error 상태까지 함께 관리합니다.
  • 액션에 따라 상태의 일부분만 바꾸기 위해 **...state**로 기존 상태 복사 후 필요한 부분만 변경해요.
  • 비동기 작업(예: 서버 호출) 시작과 성공, 실패 상황을 액션으로 처리합니다.
  • 로딩 중에는 버튼을 비활성화해서 중복 클릭 방지합니다.
  • 에러 메시지도 보여줍니다.

 

useState일 때 (코드가 더 복잡해짐)

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const increment = () => {
    setCount(count + 1);
  };

  const decrement = () => {
    setCount(count - 1);
  };

  const simulateLoading = () => {
    setLoading(true);
    setError(null);

    setTimeout(() => {
      // 성공 시
      setLoading(false);

      // 실패 시 (주석 해제해서 테스트 가능)
      // setLoading(false);
      // setError('오류 발생!');
    }, 1000);
  };

  return (
    <div>
      <h2>Count: {count}</h2>
      {loading && <p>로딩 중...</p>}
      {error && <p style={{ color: 'red' }}>에러: {error}</p>}
      <button onClick={decrement} disabled={loading}>-</button>
      <button onClick={increment} disabled={loading}>+</button>
      <button onClick={simulateLoading} disabled={loading}>비동기 작업 시작</button>
    </div>
  );
}

export default Counter;

 

 

 

// 꼭 바깥에 function으로 reduce를 안꺼내고 익명 함수로 사용해도 됨

const [savedFilter, setSavedFilter] = useReducer(
  (container, action) => {
  
  // reducer 익명 함수
    switch (action.type) {
      case SAVE_FILTER:
        setItem(`${whatPages}_${company.get.id}_filter`, JSON.stringify(action.container));
        showNotification(t('필터'), t('workingLog.appliedFilterSaved'), 'success');
        return action.container;
      case DELETE_FILTER:
        removeItem(`${whatPages}_${company.get.id}_filter`);
        showNotification(t('필터'), t('common.filterDeleted'), 'success');
        return null;
      default:
        return container;
    }
  },
  
  // 초기 상태 값
  JSON.parse(getItem(`${whatPages}_${company.get.id}_filter`))
);

 

 

728x90