개발/🟦 React
🟦[React] useReducer
비니_
2025. 6. 10. 09:17
728x90
간단한 상태는 useState 사용
복잡한 경우 useReducer 사용
ex) const initialState = { count: 0, loading: false, error: null };
import React, { useReducer } from 'react';
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:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h2>Count: {state.count}</h2>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
</div>
);
}
export default Counter;
const [state, dispatch] = useReducer(reducer, initialState);
- state: 현재 상태값을 담고 있는 객체
- dispatch: 액션을 보내는 함수
- reducer: 액션에 따라 상태를 어떻게 바꿀지 정의한 함수
- initialState: 처음 상태
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
dispatch({ type: 'INCREMENT' })를 호출하면:
- reducer(state, { type: 'INCREMENT' }) 실행됨
- 새로운 상태 { count: state.count + 1 } 반환
- 그 상태가 state에 저장되고 리렌더링됨
✅ 흐름 정리 (비유)
| 1. 초기상태 | count: 0 으로 시작 |
| 2. 액션 발생 | dispatch({ type: 'INCREMENT' }) |
| 3. reducer 동작 | 상태를 +1 증가시켜 새 객체 반환 |
| 4. 컴포넌트 리렌더링 | state.count 값이 갱신됨 |
User 클릭 → dispatch() 호출 → reducer 실행 → 새로운 state 반환 → 컴포넌트 다시 렌더링
복잡한 경우 예시
import React, { useReducer } from 'react';
const initialState = {
count: 0,
loading: false,
error: null,
};
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'LOADING_START':
return { ...state, loading: true, error: null };
case 'LOADING_SUCCESS':
return { ...state, loading: false };
case 'LOADING_ERROR':
return { ...state, loading: false, error: action.payload };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
// 버튼 클릭 시 모의 비동기 작업 예시
const simulateLoading = () => {
dispatch({ type: 'LOADING_START' });
setTimeout(() => {
// 성공 시
dispatch({ type: 'LOADING_SUCCESS' });
// 실패 시 (주석 해제하고 테스트 가능)
// dispatch({ type: 'LOADING_ERROR', payload: '오류 발생!' });
}, 1000);
};
return (
<div>
<h2>Count: {state.count}</h2>
{state.loading && <p>로딩 중...</p>}
{state.error && <p style={{ color: 'red' }}>에러: {state.error}</p>}
<button onClick={() => dispatch({ type: 'DECREMENT' })} disabled={state.loading}>-</button>
<button onClick={() => dispatch({ type: 'INCREMENT' })} disabled={state.loading}>+</button>
<button onClick={simulateLoading} disabled={state.loading}>비동기 작업 시작</button>
</div>
);
}
export default Counter;
이 예시 핵심
- count뿐 아니라 loading, error 상태까지 함께 관리합니다.
- 액션에 따라 상태의 일부분만 바꾸기 위해 **...state**로 기존 상태 복사 후 필요한 부분만 변경해요.
- 비동기 작업(예: 서버 호출) 시작과 성공, 실패 상황을 액션으로 처리합니다.
- 로딩 중에는 버튼을 비활성화해서 중복 클릭 방지합니다.
- 에러 메시지도 보여줍니다.
useState일 때 (코드가 더 복잡해짐)
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const increment = () => {
setCount(count + 1);
};
const decrement = () => {
setCount(count - 1);
};
const simulateLoading = () => {
setLoading(true);
setError(null);
setTimeout(() => {
// 성공 시
setLoading(false);
// 실패 시 (주석 해제해서 테스트 가능)
// setLoading(false);
// setError('오류 발생!');
}, 1000);
};
return (
<div>
<h2>Count: {count}</h2>
{loading && <p>로딩 중...</p>}
{error && <p style={{ color: 'red' }}>에러: {error}</p>}
<button onClick={decrement} disabled={loading}>-</button>
<button onClick={increment} disabled={loading}>+</button>
<button onClick={simulateLoading} disabled={loading}>비동기 작업 시작</button>
</div>
);
}
export default Counter;
// 꼭 바깥에 function으로 reduce를 안꺼내고 익명 함수로 사용해도 됨
const [savedFilter, setSavedFilter] = useReducer(
(container, action) => {
// reducer 익명 함수
switch (action.type) {
case SAVE_FILTER:
setItem(`${whatPages}_${company.get.id}_filter`, JSON.stringify(action.container));
showNotification(t('필터'), t('workingLog.appliedFilterSaved'), 'success');
return action.container;
case DELETE_FILTER:
removeItem(`${whatPages}_${company.get.id}_filter`);
showNotification(t('필터'), t('common.filterDeleted'), 'success');
return null;
default:
return container;
}
},
// 초기 상태 값
JSON.parse(getItem(`${whatPages}_${company.get.id}_filter`))
);
728x90