React는 DOM을 직접 조작하는 것이 아니라 JSX 문법으로 이루어진 코드로 가상 DOM을 생성하여 실제 DOM에 변경 사항을 반영한다. 이렇게 함으로써 불필요한 렌더링을 줄일 수 있는데 이 가상 DOM을 만들 때 상태 값 변화에 따라 컴포넌트가 재구성되고 리렌더링이 이루어진다.
그래서 상태 관리는 매우 중요하며 hook과 라이브러리를 통해서 다양하게 관리할 수 있다. 많은 상태관리 방법 중 useState와 useReducer가 있다.
state
를 관리해주는 함수아래 예제는 useState를 사용하여 장바구니 물품 개수를 관리한다.
1. 버튼 클릭시 setter 함수를 호출하여 상태값을 변경한다.
2. 상태값 변경으로 화면이 리렌더링된다.
const [cart, setCart] = useState(0);
const onAddHandler = () => {
alert('10개 이상은 추가할 수 없습니다.');
return;
}
setCart(prevCart => prevCart + 1);
return (
<div>
<div>장바구니 물품 {cart}개</div>
<button onClick={onAddHandler}>추가</button>
</div>
);
처음에 설정해주는 값
초기 state를 지연해서 생성
아래 예제는 useReducer를 사용하여 장바구니 물품 개수를 관리한다.
1. 버튼 클릭 시 dispatch 함수를 호출하여 타입이 ADDCART인 action 객체 전달을 한다.
2. reducer 함수가 action 객체를 전달받아 타입이 같은 케이스를 찾아 상태 변경 후 반환한다.
3. 상태가 변경되고 리렌더링이 된다.
// 초기값
const initialState = { cart: 0 };
// reducer 함수
const reducer = (state, action) => {
switch(action.type) {
case 'ADDCART':
if(cart > 10) {
throw new Error('10개 이상은 추가할 수 없습니다.');
}
return { cart: state.cart + 1 };
}
};
// App 컴포넌트
const App = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const onAddHandler = () => dispatch({ type: 'ADDCART' });
return (
<div>
<div>장바구니 물품 {state.cart}개</div>
<button onClick={onAddHandler}>추가</button>
</div>
)
}
위의 동일한 로직을 useState와 useReducer로 작성한 것을 보면 간단한 로직이기 때문에 어떻게 보면 useState로만 작성해도 된다.
하지만 onAddHandler 함수를 보면 useReducer를 사용하여 작성한 코드가 useState로 작성한 코드보다 간결하다. 그리고 type을 통해 어떤 기능을 구현하고 있는지 나타내 가독성 측면에서도 좋다.
/// useState를 사용한 경우
const onAddHandler = () => {
if(cart > 10) {
throw new Error('10개 이상은 추가할 수 없습니다.');
}
setCart(prevCart => prevCart + 1);
}
// useReducer를 사용한 경우
const onAddHandler = () => dispatch({ type: 'ADDCART' });
위와 같은 비즈니스 로직이 한두 개가 아니고 점점 많아질 가능성이 있다면 아래와 같이 useReducer를 사용하게 되면 일관성 있는 패턴으로 코드를 작성할 수 있다.
const OnAddHandler = () => dispatch({ type: 'ADDCART' });
const OnDeleteHandler = () => dispatch({ type: 'DELETECART' });
비즈니스 로직이 훨씬 더 길어지고 프로젝트의 규모가 클 경우 useReducer를 사용하면 간결하고 어떤 로직을 실행하고 있는지 한눈에 파악할 수 있으며 일관성 있게 코드를 작성할 수 있다.