노트

useReducer

useReducer

프론트엔드#react · 연결된 개념 6개

쉽게 말하면

useReducer는 통장 잔액을 직접 고치지 않고 '입금 만 원'처럼 거래를 넣으면 규칙에 따라 새 잔액이 계산되게 하는 훅이에요. 함께 바뀌는 상태와 복잡한 변경 로직을 한곳에 모아 줘요.

비유의 한계 통장은 거래 내역이 쌓여 남지만 useReducer는 계산된 최신 상태만 들고 있어요. 그래도 액션이 '무슨 일이 일어났는지'를 드러내고, 리듀서는 순수 함수라 컴포넌트 없이 테스트하기 쉬워요.

useReducer는 "이전 상태와 액션을 받아 새 상태를 돌려주는" 리듀서(Reducer) 함수로 상태를 관리하는 훅이다. 이름처럼 Array.prototype.reduce의 아이디어를 시간축으로 늘린 것이다. 배열 요소 대신 시간순으로 들어오는 액션을 누적한다.

reduce:      (acc,   current) => newAcc
useReducer:  (state, action ) => newState   → 새 state면 리렌더
type Action = { type: 'increment' } | { type: 'set'; payload: number }
 
function reducer(state: number, action: Action) {
  switch (action.type) {
    case 'increment': return state + 1
    case 'set': return action.payload
  }
}
const [count, dispatch] = useReducer(reducer, 0)
dispatch({ type: 'set', payload: 10 })

useState와 고르는 기준

  • 단순한 값(토글, 카운터), 서로 독립된 값 → useState(React 상태 갱신)
  • 함께 바뀌는 상태 묶음(isLoading·data·error) → reducer에서 한 번에 바꾼다. 불가능한 조합을 막으려면 상태 자체를 판별 유니언으로 모델링한다
  • 변경 로직이 복잡할 때 → 로직이 컴포넌트에 흩어지지 않고 reducer 하나에 모인다
  • "무슨 일이 일어났는지"가 중요할 때 → setStatus('approved') 대신 dispatch({ type: 'APPROVE', approvedBy })처럼 의도를 남긴다. 명령을 객체로 만드는 커맨드 패턴과 닮았다
  • reducer는 순수 함수(Pure Function)라서 컴포넌트 없이 단위 테스트하기 쉽다(자가 테스트 코드)

실행 취소·다시 실행(undo/redo)

상태를 past(지난 상태 목록)·present(지금)·future(되돌린 상태 목록) 셋으로 나누면 reducer 하나로 undo/redo가 된다.

type History<T> = { past: T[]; present: T; future: T[] }
 
function undoable<T>(state: History<T>, action: { type: 'UNDO' | 'REDO' } | { type: 'SET'; value: T }): History<T> {
  const { past, present, future } = state
  switch (action.type) {
    case 'UNDO':
      if (past.length === 0) return state
      return { past: past.slice(0, -1), present: past[past.length - 1], future: [present, ...future] }
    case 'REDO':
      if (future.length === 0) return state
      return { past: [...past, present], present: future[0], future: future.slice(1) }
    case 'SET': // 새 행동
      return { past: [...past, present], present: action.value, future: [] }
  }
}
  • 새 행동을 하면 future를 비운다. 되돌린 뒤 다른 길로 갔으니 예전 redo 경로는 더 이상 유효하지 않다
  • 이 방식은 상태 스냅샷을 쌓는다. 커맨드 패턴은 명령 객체마다 undo()를 두고 명령을 쌓는다. 스냅샷은 구현이 단순하지만 상태가 크고 기록이 길면 메모리를 더 쓸 수 있다

전역으로 키운 형태가 Redux다(Redux 이후의 상태 관리 선택).

출처: Redux — Implementing Undo History · Redux — Handling Other Actions

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • useActionState와 폼 액션

    React 19에서는 <form action={함수}>에 함수를 바로 넘길 수 있고, useActionState가 그 액션의 대기 상태와 마지막 결과를 한 훅으로 관리한다. useState + onSubmit + 로딩 플래그로 짜던 패턴을 대체한다.

  • useTransition

    useTransition은 특정 상태 업데이트를 "급하지 않음(transition)"으로 표시해, 그 렌더 중에도 입력 같은 긴급한 업데이트가 먼저 처리되게 하는 훅이다. [isPending, startTransition]을 반환한다.

  • 변수·컬렉션 캡슐화

    넓은 범위에서 쓰이는 데이터에 직접 접근하지 못하게 하고, 읽고 쓰는 함수를 통해서만 다루게 하는 리팩터링. 데이터를 옮기거나 바꿀 때 고쳐야 할 곳이 접근 함수 한 곳으로 좁아진다.

  • Effect가 필요 없는 경우

    Effect는 외부 시스템과 동기화할 때 쓰는 탈출구(Escape Hatch)다. 렌더 중에 계산할 수 있는 값이나 사용자 이벤트 처리에 Effect를 쓰면 렌더가 한 번 더 일어나고 흐름이 꼬인다. React 공식 문서가 이런 경우를 따로 정리해 두었다.

  • 상태 내리기와 컴포지션으로 리렌더 줄이기

    상태 내리기(State Colocation)는 state를 실제로 쓰는 컴포넌트 가까이로 옮겨, state가 바뀔 때 다시 렌더되는 범위를 좁히는 기법이다. 메모이제이션(Memoization)보다 먼저 시도할 구조적 최적화다.

보기 옵션