useReducer
는 React에서 상태 관리를 위해 사용하는 훅 중 하나로, 복잡한 상태 로직을 보다 체계적으로 관리하고 싶을 때 유용합니다. 특히 상태 변경 로직이 여러 단계로 나누어져 있거나, 상태 업데이트가 명확한 액션에 의해 이루어져야 할 때 사용됩니다. Redux의 상태 관리 방식과 유사하게 리듀서 패턴을 사용하여 상태를 업데이트합니다.
useReducer
의 기본 구조
useReducer
는 세 가지 요소를 인자로 받습니다:
- 리듀서 함수: 현재 상태와 액션을 받아서 새로운 상태를 반환하는 함수입니다.
- 초기 상태: 상태의 초기 값입니다.
- 초기화 함수 (선택적): 초기 상태를 더 복잡한 방식으로 설정해야 할 경우에 사용할 수 있습니다.
useReducer
는 dispatch
함수를 반환하며, 이 dispatch
함수는 상태를 변경하는 액션을 트리거할 때 사용됩니다.
useReducer
기본 사용법
import { useReducer } from 'react';
// 1. 리듀서 함수 정의 (현재 상태와 액션을 받아 새로운 상태를 반환)
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
};
const Counter = () => {
// 2. useReducer 훅 사용 (리듀서 함수와 초기 상태를 전달)
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
{/* 3. dispatch를 통해 상태 업데이트 */}
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
};
구성 요소 설명:
- 리듀서 함수:
reducer(state, action)
는 상태 변경의 로직을 담고 있습니다. 주로 switch 문을 사용해 액션의 타입에 따라 상태를 어떻게 업데이트할지를 정의합니다. - 초기 상태:
useReducer(reducer, { count: 0 })
에서{ count: 0 }
는 초기 상태입니다. - dispatch 함수:
dispatch({ type: 'increment' })
는 리듀서 함수에 정의된 액션을 실행하는 함수로, 상태 업데이트를 트리거합니다.
useReducer
를 사용하는 경우
- 상태가 복잡한 경우:
useState
로는 관리하기 어려울 정도로 복잡한 상태를 다뤄야 할 때useReducer
가 적합합니다. 예를 들어, 상태가 여러 속성을 포함하고 있거나, 상태 업데이트가 복잡한 규칙에 따라 이루어져야 하는 경우입니다.- 여러 개의 상태 변수를 관리해야 하거나, 상태 전환이 명확한 액션에 의해 결정될 때도 유용합니다.
const formReducer = (state, action) => {
switch (action.type) {
case 'update_name':
return { ...state, name: action.payload };
case 'update_email':
return { ...state, email: action.payload };
default:
return state;
}
};
const Form = () => {
const [state, dispatch] = useReducer(formReducer, { name: '', email: '' });
return (
<form>
<input
type="text"
value={state.name}
onChange={(e) => dispatch({ type: 'update_name', payload: e.target.value })}
/>
<input
type="email"
value={state.email}
onChange={(e) => dispatch({ type: 'update_email', payload: e.target.value })}
/>
</form>
);
};
- 상태 업데이트 로직이 여러 액션을 처리해야 할 때:
- 상태를 업데이트하는 액션의 종류가 많거나 복잡한 경우,
useReducer
를 사용하면 각각의 액션을 명확하게 정의하고 상태 전환을 쉽게 관리할 수 있습니다. - 액션의 타입을 명시적으로 구분함으로써 코드 가독성도 높아집니다.
- 상태를 업데이트하는 액션의 종류가 많거나 복잡한 경우,
- 상태 전환 로직이 복잡한 경우:
- 상태가 단순히 증가, 감소하는 것이 아니라 특정 조건이나 로직에 의해 변경되는 경우에도
useReducer
가 도움이 됩니다. 예를 들어, 상태가 여러 단계로 전환되거나 조건에 따라 변화해야 한다면useReducer
로 상태 전환을 명확하게 정의할 수 있습니다. 복잡한 로직이 포함된 상태 관리는useState
로 처리하면 코드가 분산될 수 있는데,useReducer
는 이런 복잡성을 중앙에서 관리하게 해줍니다.
- 상태가 단순히 증가, 감소하는 것이 아니라 특정 조건이나 로직에 의해 변경되는 경우에도
복잡한 상태 전환 로직 예시:
const reducer = (state, action) => {
switch (action.type) {
case 'start_loading':
return { ...state, loading: true, error: null };
case 'success':
return { ...state, loading: false, data: action.payload };
case 'error':
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};
const DataFetcher = () => {
const [state, dispatch] = useReducer(reducer, {
loading: false,
data: null,
error: null,
});
const fetchData = async () => {
dispatch({ type: 'start_loading' });
try {
const response = await fetch('/api/data');
const result = await response.json();
dispatch({ type: 'success', payload: result });
} catch (error) {
dispatch({ type: 'error', payload: error.message });
}
};
return (
<div>
{state.loading ? <p>Loading...</p> : <p>Data: {JSON.stringify(state.data)}</p>}
{state.error && <p>Error: {state.error}</p>}
<button onClick={fetchData}>Fetch Data</button>
</div>
);
};
상황별로 useReducer
를 사용하는 이유:
- 복잡한 상태 업데이트 관리: 여러 상태가 복잡한 전환 로직에 따라 업데이트되어야 할 때
useReducer
를 사용하면 액션 타입에 따라 각기 다른 상태 전환을 관리하기 쉽습니다. 위 예시에서, 데이터 로딩, 성공, 에러 등의 상태를 하나의 리듀서 함수에서 관리하므로 코드가 간결해지고 유지보수가 쉬워집니다. - 상태와 액션의 명확한 매핑: 리듀서 함수는 상태와 액션의 관계를 명확히 보여줍니다. 상태 변경의 로직이 단일 함수 내에서 처리되므로, 상태 전환 로직이 분산되지 않고 한 곳에 모여 있어 디버깅과 테스트가 용이합니다.
- 상태 전환에 대한 예측 가능성:
useReducer
는 상태 전환이 항상 액션에 의해 명시적으로 발생하도록 보장합니다. 이를 통해 상태 업데이트가 예측 가능해지고, 상태 변화의 흐름을 쉽게 추적할 수 있습니다. - 초기 상태 설정이 복잡한 경우:
useReducer
는 복잡한 초기 상태 설정이 필요할 때도 유용합니다.useReducer
의 세 번째 인자로 초기화 함수를 제공하면, 초기 상태를 좀 더 복잡한 방식으로 계산하거나 설정할 수 있습니다.
useState
와의 차이점:
useState
는 간단한 상태 관리에 적합합니다. 단일 상태 값이나 간단한 상태 로직을 다룰 때 주로 사용됩니다.useReducer
는 복잡한 상태 관리와 여러 액션에 의해 상태가 전환되는 경우에 적합합니다. 여러 상태가 서로 의존하거나, 액션의 타입에 따라 상태가 다르게 변화해야 할 때 사용됩니다.
언제 useReducer
를 사용해야 하는가?:
- 상태 전환 로직이 복잡할 때: 여러 개의 상태가 상호 연관되어 있고, 상태를 변경하는 액션이 복잡하거나 다양할 때.
- Redux와 유사한 상태 관리 패턴이 필요할 때: 컴포넌트 수준에서 Redux와 비슷한 상태 관리 패턴을 구현하고 싶을 때
useReducer
가 유용합니다. - 복잡한 초기 상태 설정이 필요할 때:
useReducer
는 초기 상태를 복잡하게 설정해야 하거나 상태 변경 로직이 많을 때 적합합니다.
결론:
useReducer
는 복잡한 상태 전환을 다루는 상황에서 매우 유용한 도구입니다. 다양한 액션을 처리하고, 상태 전환이 복잡한 경우, 또는 Redux와 같은 패턴으로 상태를 관리하고 싶을 때 useReducer
를 사용하는 것이 적절합니다. 간단한 상태 관리는 useState
로 충분하지만, 상태 전환 로직이 많거나 명확한 상태 관리가 필요한 경우 useReducer
를 사용하는 것이 좋습니다.
'TIL' 카테고리의 다른 글
[241005 TIL] 컴포넌트를 커스텀 훅으로 제공하기 (0) | 2024.10.05 |
---|---|
[241004 TIL] supabase db 복원하기 (0) | 2024.10.04 |
[240930 TIL] 비동기, 이벤트루프, 큐 (2) | 2024.10.02 |
[240929 TIL] 간단정규식으로 파일명에 특정문자열 추가하기 (0) | 2024.09.29 |
[240927 TIL] tailwind-merge, clsx 결합 사용 (1) | 2024.09.27 |