🖍️ useReducer 사용하는 경우
- React의 상태 관리 훅 중 하나
- 복잡한 상태 로직을 컴포넌트에서 분리
- 다수의 하위 값이 포함된 상태를 다룰 때 유용
- 특히 여러 상태가 서로 의존적일 때
- 다음 상태가 이전 상태에 의해 계산되어야 할 때 권장
- useState 보다 더 선언적인 방법으로 상태 업데이트 로직을 외부에 두고 싶을 때 유리
구조
reducer
- 애플리케이션의 상태를 변경하는 함수
- 이전 상태와 액션을 인자로 받아 새 상태를 반환
dispatch
- 액션을 발생시키는 함수
- 이 함수를 통해 reducer에 액션을 전달
action
- 상태 업데이트를 위한 정보를 담은 객체
- 일반적으로 type 필드를 포함
기능과 효과
상태 로직의 분리와 재사용성
- reducer 함수를 컴포넌트 외부에 정의함
- 상태 업데이트 로직을 컴포넌트로부터 분리
- 이는 코드의 가독성을 향상시키고 로직의 재사용성을 높임
예측 가능한 상태 관리
- 모든 상태 변화가 reducer 함수를 통해 발생
- 상태 변화를 더 쉽게 추적하고 예측
디버깅 용이
import React, { useReducer } from 'react';
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
};
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</div>
);
};
export default Counter;
import React, { useReducer, useState } from 'react';
const todoReducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return [...state, { id: Date.now(), text: action.payload, completed: false }];
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo
);
case 'DELETE_TODO':
return state.filter(todo => todo.id !== action.payload);
default:
throw new Error();
}
};
const TodoList = () => {
const [text, setText] = useState('');
const [todos, dispatch] = useReducer(todoReducer, []);
const handleAddTodo = () => {
dispatch({ type: 'ADD_TODO', payload: text });
setText('');
};
return (
<div>
<input type="text" value={text} onChange={(e) => setText(e.target.value)} />
<button onClick={handleAddTodo}>Add Todo</button>
<ul>
{todos.map(todo => (
<li key={todo.id} onClick={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}>
{todo.completed ? <s>{todo.text}</s> : todo.text}
</li>
))}
</ul>
</div>
);
};
import React, { useReducer } from 'react';
const authReducer = (state, action) => {
switch (action.type) {
case 'LOGIN':
return { ...state, isAuthenticated: true, user: action.payload };
case 'LOGOUT':
return { ...state, isAuthenticated: false, user: null };
case 'UPDATE_PROFILE':
return { ...state, user: { ...state.user, ...action.payload } };
default:
throw new Error();
}
};
const initialAuthState = {
isAuthenticated: false,
user: null
};
const AuthComponent = () => {
const [state, dispatch] = useReducer(authReducer, initialAuthState);
const handleLogin = () => {
dispatch({ type: 'LOGIN', payload: { username: 'user1' } });
};
const handleLogout = () => {
dispatch({ type: 'LOGOUT' });
};
return (
<div>
{state.isAuthenticated ? (
<div>
<p>Welcome, {state.user.username}!</p>
<button onClick={handleLogout}>Logout</button>
</div>
) : (
<button onClick={handleLogin}>Login</button>
)}
</div>
);
};
import React, { useReducer } from 'react';
const themeReducer = (state, action) => {
switch (action.type) {
case 'TOGGLE_THEME':
return { ...state, theme: state.theme === 'dark' ? 'light' : 'dark' };
default:
throw new Error();
}
};
const ThemeSwitcher = () => {
const [themeState, dispatch] = useReducer(themeReducer, { theme: 'light' });
const toggleTheme = () => {
dispatch({ type: 'TOGGLE_THEME' });
};
return (
<div style={{ background: themeState.theme === 'dark' ? 'black' : 'white', color: themeState.theme === 'dark' ? 'white' : 'black' }}>
<p>Current theme is {themeState.theme}</p>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
};
import React, { useReducer } from 'react';
const paginationReducer = (state, action) => {
switch (action.type) {
case 'SET_CURRENT_PAGE':
return { ...state, currentPage: action.payload };
case 'SET_TOTAL_COUNT':
return { ...state, totalCount: action.payload };
case 'SET_PAGE_SIZE':
return { ...state, pageSize: action.payload };
default:
throw new Error();
}
};
const PaginationComponent = () => {
const [paginationState, dispatch] = useReducer(paginationReducer, { currentPage: 1, pageSize: 10, totalCount: 100 });
const goToPage = (pageNumber) => {
dispatch({ type: 'SET_CURRENT_PAGE', payload: pageNumber });
};
return (
<div>
<p>Current Page: {paginationState.currentPage}</p>
<button onClick={() => goToPage(paginationState.currentPage - 1)}>Previous</button>
<button onClick={() => goToPage(paginationState.currentPage + 1)}>Next</button>
</div>
);
};
import React, { useReducer, useEffect } from 'react';
const dataFetchReducer = (state, action) => {
switch (action.type) {
case 'FETCH_INIT':
return { ...state, isLoading: true, isError: false };
case 'FETCH_SUCCESS':
return { ...state, isLoading: false, data: action.payload };
case 'FETCH_FAILURE':
return { ...state, isLoading: false, isError: true };
default:
throw new Error();
}
};
const DataFetchingComponent = ({ url }) => {
const [fetchState, dispatch] = useReducer(dataFetchReducer, { isLoading: true, data: null, isError: false });
useEffect(() => {
const fetchData = async () => {
dispatch({ type: 'FETCH_INIT' });
try {
const response = await fetch(url);
const result = await response.json();
dispatch({ type: 'FETCH_SUCCESS', payload: result });
} catch (error) {
dispatch({ type: 'FETCH_FAILURE' });
}
};
fetchData();
}, [url]);
return (
<div>
{fetchState.isError && <p>Error fetching data.</p>}
{fetchState.isLoading ? <p>Loading...</p> : <pre>{JSON.stringify(fetchState.data, null, 2)}</pre>}
</div>
);
};