useReducer와 useContext를 함께 사용하면 강력한 전역 상태 관리 시스템을 구축할 수 있습니다. 이 조합은 특히 중간 규모의 애플리케이션에서 Redux와 같은 외부 라이브러리 없이도 효과적인 전역 상태 관리를 가능하게 합니다.
다음은 useReducer와 useContext를 결합하여 간단한 전역 상태 관리 시스템을 구현하는 예제입니다:
import React, { createContext, useContext, useReducer } from 'react';
// 1. Context 생성
const TodoContext = createContext();
// 2. 초기 상태 정의
const initialState = {
todos: [],
loading: false,
error: null
};
// 3. 리듀서 함수 정의
function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, action.payload]
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo
)
};
case 'SET_LOADING':
return { ...state, loading: action.payload };
case 'SET_ERROR':
return { ...state, error: action.payload };
default:
return state;
}
}
// 4. Context Provider 컴포넌트 생성
export function TodoProvider({ children }) {
const [state, dispatch] = useReducer(todoReducer, initialState);
// 5. 최적화를 위한 메모이제이션된 값
const value = React.useMemo(() => [state, dispatch], [state]);
return <TodoContext.Provider value={value}>{children}</TodoContext.Provider>;
}
// 6. 커스텀 훅 생성
export function useTodo() {
const context = useContext(TodoContext);
if (!context) {
throw new Error('useTodo must be used within a TodoProvider');
}
return context;
}
// 7. 사용 예시
function TodoList() {
const [state, dispatch] = useTodo();
const { todos, loading, error } = state;
// 할 일 추가 함수
const addTodo = (text) => {
dispatch({ type: 'ADD_TODO', payload: { id: Date.now(), text, completed: false } });
};
// 할 일 토글 함수
const toggleTodo = (id) => {
dispatch({ type: 'TOGGLE_TODO', payload: id });
};
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<ul>
{todos.map(todo => (
<li
key={todo.id}
onClick={() => toggleTodo(todo.id)}
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
>
{todo.text}
</li>
))}
</ul>
<button onClick={() => addTodo('New Todo')}>Add Todo</button>
</div>
);
}
// 8. 앱 컴포넌트
function App() {
return (
<TodoProvider>
<h1>Todo List</h1>
<TodoList />
</TodoProvider>
);
}
이 예제에 대한 자세한 설명:
장점
1. 간결성: Redux에 비해 보일러플레이트 코드가 적습니다.
2. 학습 곡선: React의 내장 API만을 사용하므로 학습이 용이합니다.
3. 유연성: 애플리케이션의 필요에 따라 쉽게 확장할 수 있습니다.
4. 성능: 필요한 컴포넌트만 리렌더링되도록 최적화할 수 있습니다.
단점
1. 복잡성 관리: 대규모 애플리케이션에서는 상태 관리가 복잡해질 수 있습니다.
2. 미들웨어 지원 부족: Redux의 강력한 미들웨어 생태계를 활용할 수 없습니다.
3. 도구 지원: Redux DevTools와 같은 강력한 개발 도구를 사용할 수 없습니다.
성능 최적화 팁
1. 상태 분할: 큰 상태 객체를 여러 개의 작은 context로 나누어 관리합니다.
2. 메모이제이션: React.memo, useMemo, useCallback을 활용하여 불필요한 리렌더링을 방지합니다.
3. 선택적 구독: 필요한 상태만 선택적으로 구독하도록 최적화합니다.
const { todos } = useTodo(state => state.todos); // 선택적 구독
const addTodo = (text) => ({ type: 'ADD_TODO', payload: { id: Date.now(), text, completed: false } });
const toggleTodo = (id) => ({ type: 'TOGGLE_TODO', payload: id });
useEffect(() => {
dispatch({ type: 'SET_LOADING', payload: true });
fetchTodos()
.then(todos => dispatch({ type: 'SET_TODOS', payload: todos }))
.catch(error => dispatch({ type: 'SET_ERROR', payload: error.message }))
.finally(() => dispatch({ type: 'SET_LOADING', payload: false }));
}, []);
const selectTodos = state => state.todos;
const selectLoading = state => state.loading;
function TodoCount() {
const [state] = useTodo();
const todos = selectTodos(state);
return <div>Todo Count: {todos.length}</div>;
}
function logger(dispatch) {
return action => {
console.log('dispatching', action);
let result = dispatch(action);
console.log('next state', result);
return result;
};
}
// Provider 내부에서
const [state, baseDispatch] = useReducer(todoReducer, initialState);
const dispatch = logger(baseDispatch);
useReducer와 useContext의 조합은 React 애플리케이션에서 강력하고 유연한 상태 관리 솔루션을 제공합니다. 이 방법은 복잡한 상태 로직을 관리하면서도, 컴포넌트 간의 결합도를 낮추고 코드의 재사용성을 높일 수 있습니다.
특히 중소 규모의 애플리케이션에서 이 패턴은 Redux와 같은 외부 라이브러리의 좋은 대안이 될 수 있습니다. 그러나 매우 큰 규모의 애플리케이션이나 더 복잡한 상태 관리가 필요한 경우에는 여전히 Redux나 MobX와 같은 전문적인 상태 관리 라이브러리를 고려해볼 수 있습니다.
useReducer와 useContext를 효과적으로 활용하면, React의 내장 기능만으로도 강력하고 확장 가능한 상태 관리 시스템을 구축할 수 있습니다.
#ReactHooks #useReducer #useContext #StateManagement #ReactPatterns #ContextAPI #GlobalState #ReactPerformance