리액트는 상태관리를 위해 useState와 useReducer라는 두 종류의 훅을 제공한다. useReducer를 주제로 하는 글이지만 리액트에서 왜 같은 역할을 하는 훅을 두 종류나 제공하는지 의문이 들 수 있다. 기실 useState와 비교해보는 것이 useReducer의 이해에 도움이 되기도 한다.
따라서 본문에서는
두 훅의 생김새는 다음과 같다.
const [state, setState] = useState(initialValue);
const [state, dispatch] = useReducer(reducer, initialValue);
// 해당 글에서는 주제상 useReducer의 세 번째 매개변수 init은 무시한다.
이들의 공통점은 세 가지다.
반면 차이점은 두 가지가 있다.
이런 차이점들은 두 훅에서 상태를 변경하는 DataFlow가 다르기 때문에 발생한다.

시작하기에 앞서 상단에 게시된 gif는 리덕스 공식문서에서 리덕스 내부의 데이터 흐름을 가시화해둔 것이다. Reducer함수를 감싸고 있는 Store만 제외하면 useReducer훅에서 일어나는 데이터 흐름과 동일하다.
상세에 대해서는 차후 재조명할 예정이므로 지금은 한 번 살펴만 보고 넘기자.
일반적으로 useState의 DataFlow는 다음과 같다.
// 예시는 하단 참고링크 How to use React useReducer hook like a pro에서 발췌
const [state, setState] = useState(initialValue);
/*
* setState는 단순하게 기존의 state를 넘겨받은 매개변수(parameter)로 교체한다.
* 때문에 값을 산출하는 로직은 setCount( 매개변수 내부에 ) 작성되었다.
* prevCount => prevCount + 1은 값으로 평가할 수 있는 콜백 함수이므로
* setCount( number )와 동일하다.
*/
<button onClick={() => setCount(prevCount => prevCount + 1)}>
+
</button>
반면 useReducer에는 한 단계가 추가된다.
const [state, dispatch] = useReducer(reducer, initialValue);
/*
* 값을 산출하고 state를 교체하는 로직은 모두 dispatch가 아니라 reducer함수가 담당한다.
* 로직이 모두 reducer함수에 모여있기 때문에 보다 복잡한 state를 다루기에 유리하다.
*/
<button onClick={() => dispatch({ type: 'usename', payload: '홍길동' })}>
+
</button>
두 훅의 동작에 큰 차이점은 없지만 위와 같이 새로운 용어가 다수 등장하여 혼란스럽다. 일단 dispatch가 reducer함수에게 액션 객체를 전달하는 역할밖에 하지 않는다는 것은 이해할 수 있을 것이다.
그러므로 다음은 중요한 reducer함수와 액션 객체에 대해 살펴보자.
reducer함수는 일반적으로 switch문을 이용하여 작성한다.
const reducer = (state, action) => {
/*
* ex : dispatch({ type: 'usename', payload: '홍길동' })
* 1. 전달받은 액션객체에서 type을 확인한다.
* 2. action.type은 'username'이므로 해당 case를 작동시킨다.
* 3. 기존 state를 담고 username에 payload를 전달해 새로운 객체를 반환한다.
* (return {...state, username: action.payload })
*/
switch (action.type) {
case 'username':
return { ...state, username: action.payload };
case 'email':
return { ...state, email: action.payload };
default:
throw new Error(`Unknown action type: ${action.type}`);
}
};
위에서 확인했다시피 액션 객체는 { type: '...', payload: '...' }형태의 객체다. 단, payload는 부가적인 요소로 요청의 특성에 따라 반드시 전달할 필요는 없다.
양 쪽 모두 일반적인 용례일 뿐 반드시 해당 이름일 필요는 없지만 리덕스의 경우 type필드를 필수적으로 요구하므로 특별한 이유가 없다면 따라주자.
아래는 useReducer훅의 전체 예시이다. 한 번 살펴보고 DataFlow의 그림을 다시 한 번 살펴보자.
// 하단 How to use React useReducer hook like a pro링크 발췌
import { useReducer } from 'react';
const initialValue = {
username: '',
email: '',
};
const reducer = (state, action) => {
switch (action.type) {
case 'username':
return { ...state, username: action.payload };
case 'email':
return { ...state, email: action.payload };
default:
throw new Error(`Unknown action type: ${action.type}`);
}
};
const Form = () => {
const [state, dispatch] = useReducer(reducer, initialValue);
return (
<div>
<input
value={state.username}
onChange={(event) =>
dispatch({ type: 'username', payload: event.target.value })
}
/>
<input
value={state.email}
onChange={(event) =>
dispatch({ type: 'email', payload: event.target.value })
}
/>
</div>
);
};
export default Form;

useReducer는