useReducer는 컴포넌트의 상태 관리 로직을 더 구조적으로 다룰 때 사용하는 Hook이다.
useState와 비슷한 기능을 제공하지만, 더 구조적이라고 생각해도 좋을 것 같다. useState의 setState 함수를 외부 함수로 분리할 수 있다는 것이 가장 큰 특징이다.
1. Dispatch, Action, Reducer
Get XQ’s stories in your inbox
Join Medium for free to get updates from this writer.
useReducer는 Redux 패턴에서 온 아래 3가지 용어를 사용한다.
- Dispatch: 상태(
state) 변경 요청을 보내는 함수 - Action: 상태(
state)의 변경 로직을 적어놓은 객체 - Reducer: 현재 상태(
state)와 행동(action)을 받아서 새로운 상태를 반환하는 함수
레퍼런스
useReducer(reducer, initialArg, init?)
const [state, dispatch] = useReducer(reducer, initialArg, init?);매개변수
reducer: state가 어떻게 업데이트 되는지 지정하는 Reducer 함수. Reducer 함수는 반드시 순수 함수여야 하며, State와 Action을 인수로 받아야 하고, 다음 State를 반환해야 한다. State와 Action에는 모든 데이터 타입이 할당될 수 있다.initialArg: 초기 State가 계산되는 값이다. 모든 데이터 타입이 할당될 수 있다. 초기 State가 어떻게 계산되는지는 다음init인수에 따라 달라진다.- 선택사항
init: 초기 State를 반환하는 초기화 함수다. 이 함수가 인수에 할당되지 않으면 초기 State는initialArg로 설정된다. 할당되었다면 초기 State는init(initialArg)를 호출한 결과가 할당된다.
반환값
useReducer는 2개의 엘리먼트로 구성된 배열을 반환한다.
- 현재 state. 첫번째 렌더링에서의 state는
init(initialArg)또는initialArg로 설정된다 (init이 없을 경우initialArg로 설정된다). dispatch함수.dispatch는 state를 새로운 값으로 업데이트하고 리렌더링을 일으킨다.
주의 사항
useReducer는 Hook이므로 컴포넌트의 최상위 또는 커스텀 Hook에서만 호출할 수 있다. 반복문이나 조건문에서는 사용할 수 없다. 필요한 경우 새로운 컴포넌트를 추출하고 해당 컴포넌트로 State를 옮겨서 사용할 수 있다.dispatch함수는 안정된 식별성을 가지고 있기 때문에, 흔히 Effect의 의존성에서 제외하는 것을 볼 수 있다. 하지만 포함해도 Effect를 실행하지는 않는다. 린터에서 의존성을 생략해도 오류가 발생하지 않는다면, 그렇게 하는 것이 안전하다. Effect 의존성 제거에 대해 자세히 알아보자.- Strict Mode에서는 우연한 비순수성을 찾아내기 위해 Reducer와
init함수를 두번 호출한다. 개발 환경에서만 한정된 동작이며, 배포(Production) 환경에는 영향을 미치지 않는다. Reducer와init함수가 순수 함수라면(그래야만 하듯이) 로직에 어떠한 영향도 미치지 않는다. 호출 중 하나의 결과는 무시한다.
실습
useReducer를 사용하여 counter 컴포넌트를 만들어 보자.
// reducer는 순수 함수
function reducer(state, action) {
// action.type에 따라 로직 분리
switch (action.type) {
case 'INCREMENT':
// payload 있을 시 payload 만큼 증가
return { count: state.count + (state.payload ?? 1) };
case 'DECREMENT':
// payload 있을 시 payload 만큼 감소
return { count: state.count - (state.payload ?? 1) };
case 'RESET':
// 초기화
return { count: 0 };
default:
// 알 수 없는 타입이 들어오면 기존 생태 반환
return state;
}
}
function Counter() {
// 초기값 설정: { count: 0 }
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>count: {state.count}</p>
{/* dispatch를 통해 `Action`을 보냄 */}
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-1</button>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+1</button>
{/* payload를 통해 추가 데이터 전달 */}
<button onClick={() => dispatch({ type: 'DECREMENT', payload: 10 })}>
-10
</button>
<button onClick={() => dispatch({ type: 'INCREMENT', payload: 10 })}>
+10
</button>
<button onClick={() => dispatch({ type: 'RESET' })}>초기화</button>
</div>
);
}주의
state는 읽기 전용이다. state의 객체나 배열을 변경하면 안 된다.
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
// 🚩 Don't mutate an object in state like this:
state.count = state.count + 1;
return state;대신 reducer에서 새로운 객체를 반환한다.
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
// ✅ Instead, return a new object
return { count: state.count + 1 };자세한 내용을 공부하려면 객체 State 업데이트하기와 배열 State 업데이트하기를 읽어보자.

![[X Window][Linux] X 윈도우](https://miro.medium.com/v2/resize:fit:1358/format:webp/0*F4ntxVRVPco1h3Yr.png)


