React Hooks는 함수형 컴포넌트에서도 state와 lifecycle 기능을 사용할 수 있게 해주는 API입니다. 그중에서 useEffect와 useReducer에 대해서 알아보겠습니다.
useEffect는 컴포넌트가 랜더링된 이후에 어떠한 작업을 수행해야 할 떄 사용하는 Hook입니다.
useEffect(() => {
// 컴포넌트가 마운트되거나 업데이트될 때 실행될 로직
console.log('component rendered');
return () => {
// 컴포넌트가 언마운트될 때 실행될 로직
console.log('component will unmount');
};
}, [dependencyList]);
dependencyList에 의존성 배열을 전달하여 특정 값이 변경될 때만 effect를 실행하도록 조절할 수 있습니다.
useReducer는 복잡한 컴포넌트 상태 로직을 관리할 수 있는 Hook입니다. useState보다 더 유연한 상태 관리를 할 때 주로 사용합니다.
기본적으로, useReducer는 현재 상태와 액션 객체를 받아 새로운 상태를 반환하는 리듀서 함수를 사용합니다.
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
}
그렇다면 여기에서 useState와 useReducer는 둘 다 상태 관리를 위한 Hook인데 뭐가 다른지 궁금해 할 수도 있습니다. 아래의 내용을 보면 차이가 분명이 존재한다는 것을 알 수 있을 것입니다.
const [count, setCount] = useState(0);
복잡한 상태 로직이나 다음 상태가 이전 상태에 의존적일 때 유용합니다.
상태 값과 그 상태를 변화시키기 위한 dispatch 함수를 반환합니다.
액션을 기반으로 상태 변화 로직을 분리하여 관리할 수 있습니다.
const [state, dispatch] = useReducer(reducer, initialState);
복잡도: useState는 간단한 상태 관리에 적합하고, useReducer는 복잡한 상태 로직이나 중첩된 상태를 다룰 때 더 적합합니다.
상태 업데이트 방식: useState는 상태를 직접 설정하는 반면, useReducer는 액션을 통해 상태를 업데이트합니다.
재사용성: useReducer에서 리듀서 함수를 분리하면 로직의 재사용성이 높아집니다.
단일 값: 상태 값이 단순한 데이터 타입(예: 문자열, 숫자, 불린) 또는 간단한 객체나 배열로 구성된 경우.
독립적인 상태 업데이트: 상태 업데이트가 다른 상태나 액션의 결과에 의존하지 않는 경우.
직관적: 상태 업데이트 로직을 보았을 때, 해당 로직의 동작을 쉽게 이해할 수 있는 경우
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
중첩된 상태: 상태 값이 중첩된 객체나 복잡한 데이터 구조를 가지고 있는 경우.
의존적인 상태 업데이트: 상태 업데이트가 다른 상태의 값이나 이전 상태, 혹은 복잡한 조건 로직에 의존하는 경우.
로직의 재사용: 동일한 상태 업데이트 로직이 여러 곳에서 재사용되어야 하는 경우.
사이드 이펙트와의 관련성: 상태 업데이트가 비동기 작업, 외부 데이터 소스와의 상호작용 등의 사이드 이펙트와 밀접하게 관련된 경우.
const initialState = {
loading: false,
data: null,
error: null
};
function reducer(state, action) {
switch (action.type) {
case 'FETCH_START':
return { ...state, loading: true };
case 'FETCH_SUCCESS':
return { loading: false, data: action.payload, error: null };
case 'FETCH_ERROR':
return { ...state, loading: false, error: action.error };
default:
throw new Error();
}
}