React19.1 - State Hooks(useReducer-1)

Hunter Joe·2025년 4월 18일

📌 useReducer

Convention

const [state, dispatch] = useReducer(reducer, initialArg, init?)

Parameter

  • reducer: state가 업데이트되는 방식을 지정하는 reducer 함수이다.

    • pure function이어야 하고
    • stateaction을 arguments로 받고 nextState를 반환해야 한다.
      stateaction은 어떤 Type이든 가능하다.
  • initialArg: 초기 state를 계산하는 값. 모든 Type이 가능하다. 초기 state를 계산하는 방식은 다음 init argument에 따라 달라진다.

  • init(optional): 초기 상태를 반환해야 하는 초기화 함수
    이 함수가 명시되지 않은 경우 초기 stateinitialArg로 설정
    반면, 이 함수가 명시되었다면 초기 stateinit(initialArg)를 호출한 결과로 설정됩니다.

공식문서의 설명은 굉장히 긴데 이걸 쉽게 풀어 설명하면 다음과 같다.

Summary
reducer: state를 업데이트하는 함수
initialArg: 초기 state로 사용될 값 (또는 초기화 함수에 전달할 인자)
init(optional): 초기 state를 가공하거나 계산해서 반환하는 함수

Returns
useReducer는 2개의 값을 가진 배열을 return한다.
1. state (The current state, 현재 상태)
2. dispatch: dispatch함수는 state를 다른 값으로 업데이트하고 다시 렌더링을 트리거 할 수 있다.

Caveats
3가지 주의사항을 한번 알아보자 ↓

1. useReducer는 Hook이므로 컴포넌트의 최상위 레벨이나 커스텀 훅 안에서만 사용이 가능하다. 반복문 or 조건문 안에서는 호출 불가능,
2. dispatch는 Effect dependency에서 제거해도 안전하다. 만약 포함하더라도 Effect가 실행되지 않는다
3. Strict 모드에 대한 애기 (두 번 호출)


dispatch function

dispatchuseReducer에서 반환되는 함수로, state를 업데이트하고 리렌더링을 트리거, 이 때 dispatch의 argument로 action 객체를 인자로 넘겨야 한다.

const [state, dispatch] = useReducer(reducer, { age: 42 });

function handleClick() {
  dispatch({ type: 'incremented_age' });
  // ...

Parameter
action:

  • 사용자가 수행하는 작업
  • 모든 유형의 값이 될 수 있다
  • action은 보통 type이라는 속성을 가진 객체이며, 선택적으로 추가 정보를 담는 다른 속성들을 포함할 수 있다.

메모
Redux에서도 useReucer의 형태를 따라 만들었다고 한 문서를 봤는데
진짜 거의 비슷하네

Caveats
dispatch에도 3가지 주의 사항이 있다. ↓

  1. dispatch 함수는 다음 렌더링을 위한 state variable만을 업데이트 한다.
    만약 함수를 호출한 후 state variable를 읽어도 호출 전 화면에 있던 이전 값이 그대로 유지된다.
    → 이는 useState와 마찬가지로,state 업데이트가 일어났다고 해서 즉시 변경된 값이 반영되는 것이 아니라 다음 렌더링 이후에 반영된다는 점에서 동일
  2. state를 비교할 때 Object.is를 통해 비교하며 변경사항이 없을 경우 리렌더 X
  3. React는 state 업데이트를 batch 처리
    → 이 부분도 useState에서 나온 Caveats와 동일

Usage

Adding a reducer to a component

import { useReducer } from 'react';

function reducer(state, action) {
  if (action.type === 'incremented_age') {
    return {
      age: state.age + 1
    };
  }
  throw Error('Unknown action.');
}

export default function Counter() {
  const [state, dispatch] = useReducer(reducer, { age: 42 });

  return (
    <>
      <button onClick={() => {
        dispatch({ type: 'incremented_age' })
      }}>
        Increment age
      </button>
      <p>Hello! You are {state.age}.</p>
    </>
  );
}

useReduceruseState와 매우 흡사하나
useReducer는 이벤트 핸들러에 흩어져 있던 state 변경 로직을 컴포넌트 외부의 하나의 함수(reducer)로 분리할 수 있게 해준다.

useState vs useReducer

  • useReducer에도 단점은 존재
  1. Code size: useReducer를 사용하면 reducer 함수와 dispacth 함수를 모두 작성하기에 코드 사이즈가 커진다. 하지만, 여러 이벤트 핸들러가 비슷한 방식으로 state를 수정하는 경우 useReducer를 사용하면 코드 크기를 줄일 수 있다.
  1. Readability(가독성): state 업데이트가 단순할 때는 useState가 읽기 쉽고 간결하다.
    하지만 업데이트 로직이 복잡해지고 컴포넌트 코드가 길어지면 가독성이 떨어질 수 있다.
    이럴 때 useReducer"어떻게 상태를 업데이트할지(how)""무슨 일이 발생했는지(what happened)"를 명확히 분리해주어, 코드 구조를 더 깔끔하게 만들어준다.
  1. Debugging: useReducer를 각 함수에 로깅을 할 수 있어서 디버깅에 용이
  1. Testing: reducer는 순수 함수이고 이것은 컴포넌트에 영향이 없기에 독립적인 테스트에 용이
  1. Personal preference: 개인의 선호에 맞춰서 useState와 useReducer를 사용하면 된다. 둘은 동일하다.

React 팀은 state 업데이트를 맞이할 때 버그가 잦고 코드 구조가 복잡할 때 useReducer 사용을 권장
모든 상황에 쓸 필요는 없으며, useState와 혼용하거나 함께 사용하는 것도 가능한다고 한다.

Writing the reducer function


이것은 불변성에 관한 애기로 직접적으로상태를 변경시키지 말라는 것

profile
Improvise, Adapt, Overcome

0개의 댓글