useReducer
는 useState
보다 복잡한 상태 관리에 적합해. 특히 상태 변경이 조건에 따라 분기되거나, 여러 상태를 하나로 묶고 싶을 때 유용해.
TypeScript에선 state, action 모두 타입을 명확하게 정의해야 해.
type State = {
count: number;
};
type Action =
| { type: 'INCREMENT' }
| { type: 'DECREMENT' }
| { type: 'RESET' };
const initialState: State = { count: 0 };
✅ Action은 유니언 타입으로 정의해서 조건 분기에 강력하게 대응할 수 있어. State도 반드시 구조화해야 해.
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에 따라 자동으로 타입을 좁혀줘서 타입 안정성을 높여줘.
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 타입으로 추론돼. 명시적으로 지정하지 않아도 괜찮지만, 복잡한 앱에선 명시적으로 지정하기도 해.
// 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;
}
};
✅ 타입과 로직을 외부로 분리하면 재사용성과 테스트가 높아져. 규모가 커질수록 이런 구조가 중요해져.