useReducer 훅 소개
useReducer
는 React의 내장 훅 중 하나로, 복잡한 상태 로직을 관리하는 데 사용됩니다.
이 훅은 특히 여러 하위 값을 포함하는 복잡한 상태 객체를 다룰 때 유용합니다.
useReducer의 개념과 사용 목적
useReducer
는 현재 상태와 액션 객체를 받아 새로운 상태를 반환하는 리듀서 함수를 기반으로 작동합니다.
이는 Redux의 패턴과 유사하며, 다음과 같은 목적으로 사용됩니다.
- 복잡한 상태 로직 관리
- 관련된 상태 변화를 그룹화
- 상태 변화의 예측 가능성 향상
useState와의 차이점
useState
가 단순한 상태 관리에 적합하다면, useReducer
는 다음과 같은 경우에 더 적합합니다.
- 상태 구조가 복잡할 때
- 다음 상태가 이전 상태에 의존적일 때
- 여러 상태를 함께 업데이트해야 할 때
useReducer 사용 방법
useReducer
의 기본 구조는 다음과 같습니다.
const [state, dispatch] = useReducer(reducer, initialState);
여기서,
state
는 현재 상태dispatch
는 액션을 발생시키는 함수reducer
는 상태를 업데이트하는 함수initialState
는 초기 상태
Reducer 함수 작성 방법
Reducer 함수는 현재 상태와 액션을 인자로 받아 새로운 상태를 반환합니다.
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();
}
}
Action의 구조
Action은 일반적으로 type
프로퍼티를 가진 객체입니다.
추가 데이터가 필요한 경우 다른 프로퍼티를 포함할 수 있습니다.
{ type: 'INCREMENT' }
{ type: 'SET_COUNT', payload: 5 }
Dispatch 함수 사용법
dispatch
함수는 액션 객체를 인자로 받아 리듀서 함수를 실행시킵니다.
dispatch({ type: 'INCREMENT' });
dispatch({ type: 'SET_COUNT', payload: 5 });
복잡한 상태 관리 예제
다음은 useReducer
를 사용하여 할 일 목록을 관리하는 예제입니다.
import React, { useReducer } from 'react';
const initialState = { todos: [] };
function reducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return { todos: [...state.todos, action.payload] };
case 'TOGGLE_TODO':
return {
todos: state.todos.map((todo, index) =>
index === action.payload ? { ...todo, completed: !todo.completed } : todo
)
};
case 'REMOVE_TODO':
return {
todos: state.todos.filter((_, index) => index !== action.payload)
};
default:
throw new Error();
}
}
function TodoApp() {
const [state, dispatch] = useReducer(reducer, initialState);
function addTodo(text) {
dispatch({ type: 'ADD_TODO', payload: { text, completed: false } });
}
function toggleTodo(index) {
dispatch({ type: 'TOGGLE_TODO', payload: index });
}
function removeTodo(index) {
dispatch({ type: 'REMOVE_TODO', payload: index });
}
return (
<div>
{/* 할 일 목록 렌더링 및 상호작용 UI */}
</div>
);
}
useReducer 사용의 이점
- 예측 가능성 : 상태 변화가 리듀서 함수 내에서 명확하게 정의됩니다.
- 디버깅 용이성 : 각 액션과 그 결과로 인한 상태 변화를 쉽게 추적할 수 있습니다.
- 로직 분리 : 상태 업데이트 로직을 컴포넌트에서 분리할 수 있습니다.
- 테스트 용이성 : 리듀서 함수는 순수 함수이므로 테스트하기 쉽습니다.
주의사항
- 과도한 사용 주의 : 단순한 상태 관리에는
useState
가 더 적합할 수 있습니다. - 불변성 유지 : 리듀서 함수에서 상태를 업데이트할 때 항상 불변성을 유지해야 합니다.
- 복잡성 관리 : 리듀서 함수가 너무 복잡해지지 않도록 주의해야 합니다.
useReducer와 Context API의 결합
useReducer
와 Context API를 함께 사용하면 강력한 전역 상태 관리 솔루션을 만들 수 있습니다.
다음은 간단한 예제입니다.
import React, { createContext, useReducer, useContext } from 'react';
const StateContext = createContext();
const DispatchContext = createContext();
const initialState = { /* 초기 상태 */ };
function reducer(state, action) {
// 리듀서 로직
}
export function StateProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}
export function useState() {
return useContext(StateContext);
}
export function useDispatch() {
return useContext(DispatchContext);
}
// 사용 예:
function SomeComponent() {
const state = useState();
const dispatch = useDispatch();
// state와 dispatch 사용
}
이 패턴을 사용하면 상태와 디스패치 함수를 별도의 컨텍스트로 제공하여, 불필요한 리렌더링을 방지하고 성능을 최적화할 수 있습니다.
useReducer
는 React 애플리케이션에서 복잡한 상태 로직을 관리하는 강력한 도구입니다.
특히 여러 관련 상태를 함께 업데이트해야 하거나, 상태 변화가 이전 상태에 의존적인 경우에 유용합니다.
useState
에 비해 더 구조화된 접근 방식을 제공하며, 상태 변화의 예측 가능성과 디버깅 용이성을 향상시킵니다.
그러나 모든 상황에 useReducer
가 적합한 것은 아닙니다. 단순한 상태 관리의 경우 useState
가 여전히 좋은 선택일 수 있습니다. 상태 관리의 복잡성과 애플리케이션의 요구사항을 고려하여 적절한 훅을 선택해야 합니다.
useReducer
와 Context API를 결합하면 Redux와 유사한 전역 상태 관리 솔루션을 구현할 수 있습니다.
이 접근 방식은 특히 중간 규모의 애플리케이션에서 유용할 수 있으며, 외부 라이브러리 없이도 효과적인 상태 관리를 가능하게 합니다.
결론적으로, useReducer
는 React의 상태 관리 도구 키트에 중요한 추가 사항입니다. 이를 효과적으로 활용하면 더 체계적이고 유지보수가 용이한 React 애플리케이션을 개발할 수 있습니다.