리액트 프로젝트를 만들 때, 한 컴포넌트에서 선언 및 사용되는 state를 다른 컴포넌트에서 사용하기 위해 prop-drilling 방식을 활용할 수 있다. 그런데 이 방식은 부모 컴포넌트에서 자식 컴포넌트로 단방향으로만 state를 전달 가능하고, 그 역방향 혹은 동일 레벨상에서의 state 전달이 불가능하다. 또한 부모 - 자식 - 그 자식의 자식 - 그 자식의 자식 ... 처럼 구조의 뎁스가 깊어질수록 디버깅 및 추적의 어려움이 있다.
useContext hook을 사용하면, 특정 context의 범위 아래에 있는 모든 컴포넌트들은 prop-drilling 과정 없이 필요할 때 원하는대로 state에 접근 및 setState를 할 수 있다. 그러나 최상단의 state를 업데이트 하면 하위의 모든 컴포넌트가 함께 리렌더링되기 때문에 성능에 악영향을 줄 수 있다는 문제가 있어 상태 변화가 거의 일어나지 않을 것으로 예상되는 값을 전달할 때 적합하다.
(useMemo를 사용하여 리렌더링 비용을 최적화 할 수도 있다.)
Redux가 나온 이후에 React 자체 Hook으로 추가된 기능이라고 한다. 그래서 reducer, action, dispatch 등 관련 개념과 사용 방법이 Redux와 거의 같다.

이미지 출처: 링크텍스트
1. state 선언하기
const [state, dispatch] = useReducer(reducer, initialState);
2. Reducer 만들기 (payload 없는 경우):
reducer는 인자로 state와 action객체를 받아 state를 어떤 식으로 변화시킬 것인지를 미리 설정해 두는 곳이다.
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'FIRST_CASE':
return state + 1;
case 'SECOND_CASE:
return state - 1;
default:
return state;
}
}
3. Reducer 사용하기:
state의 변화를 일으키고자 하는 이벤트에 함수를 걸어주고 dispatch를 실행시켜 action 객체를 reducer로 던져준다. 이 때 reducer는 전달받은 action의 type에 따라 payload만큼 state를 변화시키고, 이에 따라 화면이 재렌더링 된다.
function Counter() {
const [state, dispatch] = useReducer(reducer, 0);
const Increase = () => {
dispatch({type: 'FIRST_CASE'});
};
const Decrease = () => {
dispatch({type: 'SEOCOND_CASE'});
};
return (
<>
<button onClick={Increase}> +1 </button>
<button onClick={Decrease}> -1 </button>
</>
);
}
관리하는 값이 단순한 숫자, 문자열이거나 boolean값이라면 useState로 관리해도 무방하다. 그러나, 컴포넌트에서 관리하는 값이 여러개이거나, 복잡한 구조로 되어있다면 useReducer로 관리하는 것이 편리할 수 있겠다.