const [state, dispatch] = useReducer(reducer, initialArg, init?)
reducer
: 상태가 어떻게 업데이트되는지를 지정하는 리듀서 함수. 상태와 액션을 매개변수로 받아들여 다음 상태를 반환한다.
initialArg
: 초기 상태 값. 어떻게 초기 상태가 이 값에서 계산되는지는 다음 init 매개변수에 따라 달라진다.
init(optional)
: 초기 상태를 반환하는 초기화 함수. 이 값이 지정되지 않으면 초기 상태는 initialArg로 설정된다.
const [state, setState] = useState(initialState)
초기 값을 인자로 받아, 사용하게 될 변수와 해당 변수를 업데이트해 줄 dispatcher를 반환하는 useState와 달리, useReducer는 옵셔널한 init 함수를 제외하면 초기 값(initialArg) 외 reducer라는 함수 또한 인자로 받게 된다.
위 차트는 useReducer 훅의 동작 과정을 간단하게 표현한 workflow이다.
각 버튼을 클릭했을 때 state에 1을 더하거나 빼는 동작을 하게 된다.
// useState 활용
const [state, setState] = useState(0);
const handleState = (type) => {
if (type === "increase") setState((prev) => prev + 1);
if (type === "decrease") setState((prev) => prev - 1);
};
return (
<div>
<h1>카운터</h1>
<div>{state}</div>
<button onClick={() => handleState("increase")}>increase</button>
<button onClick={() => handleState("decrease")}>decrease</button>
</div>
);
// useReducer 활용
const [state, dispatch] = useReducer(reducer, 0);
function reducer(state, action) {
if (action.type === "increase") return ++state;
if (action.type === "decrease") return --state;
}
return (
<div>
<h1>카운터</h1>
<div>{state}</div>
<button onClick={() => dispatch({ type: "increase" })}>increase</button>
<button onClick={() => dispatch({ type: "decrease" })}>decrease</button>
</div>
);
위 두 코드는 같은 기능을 한다. 두 코드 내 모두 동일하게 조건 분기에 따라 state 값을 1만큼 올려주거나 내려주는 함수(handleState와 reducer)가 존재한다.
하지만, 함수의 생김새는 다르다. 앞서 말했듯, useReducer 훅에서 사용되는 reducer 함수는 그 return 값을 통해 바인딩 된 state 값을 새롭게 업데이트한다.
반면, useState를 활용해 구현한 handleState 함수에서는 직접 state에 대한 dispatcher를 호출하여 state를 업데이트하고 있다.
state를 업데이트하기 위해 dispatcher를 호출하는 handleState(useEffect) 함수와는 달리, reducer(useReducer) 함수는 업데이트하고자 하는 새로운 값을 직접 return함으로써 state에 대한 업데이트 역할만을 수행하는 데 집중할 수 있다.
이는 함수형 프로그래밍에서 지향하는 순수 함수의 형태를 띈다고 할 수 있다.
지금은 위 코드의 비즈니스 로직이 매우 단순하여 부수 효과의 발생 혹은 버그의 발생 가능성이 적지만, 조금만 어플리케이션이 복잡해지면 사이드 이펙트의 발생 가능성은 높아진다.
// useState 활용
const handleState = (type) => {
// 추가적인 비즈니스 로직
if (type === "increase")
{
// 추가적인 비즈니스 로직
setState((prev) => prev + 1);
}
if (type === "decrease")
{
// 추가적인 비즈니스 로직
setState((prev) => prev - 1);
}
};
이처럼 하나의 함수 내에서 여러 기능을 책임지게 되면 버그가 발생했을 시 추적이 용이하지 않을 뿐더러 코드를 고치는 과정도 복잡해진다.
때문에, 상태 관리에 대한 로직이 복잡해지게 된다면 useReducer 훅을 활용해 state를 업데이트해 주는 로직을 순수 함수 형태인 reducer로 분리하여 사용하는 편이 적절할 것이다.