useReduceruseState보다 복잡한 상태 관리에 적합해. 특히 상태 변경이 조건에 따라 분기되거나, 여러 상태를 하나로 묶고 싶을 때 유용해.

TypeScript에선 state, action 모두 타입을 명확하게 정의해야 해.


📘 예제 46: 상태와 액션 인터페이스 정의

type State = {
  count: number;
};

type Action =
  | { type: 'INCREMENT' }
  | { type: 'DECREMENT' }
  | { type: 'RESET' };

const initialState: State = { count: 0 };

✅ Action은 유니언 타입으로 정의해서 조건 분기에 강력하게 대응할 수 있어. State도 반드시 구조화해야 해.


📘 예제 47: 타입 좁히기 switch

const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    case 'RESET':
      return { count: 0 };
    default:
      return state;
  }
};

✅ switch 문에서 TypeScript는 action.type에 따라 자동으로 타입을 좁혀줘서 타입 안정성을 높여줘.


📘 예제 48: Dispatch 함수 타입

import { useReducer } from 'react';

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

  return (
    <>
      <p>카운트: {state.count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+1</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-1</button>
      <button onClick={() => dispatch({ type: 'RESET' })}>초기화</button>
    </>
  );
};

✅ dispatch는 자동으로 Action 타입으로 추론돼. 명시적으로 지정하지 않아도 괜찮지만, 복잡한 앱에선 명시적으로 지정하기도 해.


📘 예제 49: reducer 타입 분리

// types.ts
export type State = { count: number };
export type Action = { type: 'INC' } | { type: 'DEC' };

// reducer.ts
export const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case 'INC':
      return { count: state.count + 1 };
    case 'DEC':
      return { count: state.count - 1 };
    default:
      return state;
  }
};

✅ 타입과 로직을 외부로 분리하면 재사용성과 테스트가 높아져. 규모가 커질수록 이런 구조가 중요해져.