Redux가 왜 필요한가. 상태관리 문제상황 알아보기
React 상태 관리의 기본 문제
React만 사용할 때는 보통 이렇게 상태를 관리를 한다.
- 컴포넌트 내부 useState
- 부모 → 자식으로 props 전달
- 필요하면 context 사용
하지만 페이지 곳곳에서 같은 데이터를 써야 한다면 react만 사용할 때의 상태관리가 복잡해진다.
예: 로그인한 사용자 정보, 장바구니, 알림 개수 등
컴포넌트 깊이가 깊어질수록
A → B → C → D → E 에 props로 상태를 계속 넘겨야 하는 'props drilling' 현상이 생긴다.
비동기 로직(API 요청, 로딩, 에러)과 상태가 섞이면서 값이 도대체 어디서 바뀌는 것인지 헷갈리게 된다.
Redux는 한마디로
'앱 전체에 하나의 중앙 창고(store)를 두고, 상태 변경 규칙을 아주 명확하게 강제하는 도구' 라고 할 수 있다.
Redux 핵심 개념: store / action / reducer / dispatch / 단방향 흐름
Redux는 5개 단어만 정확히 이해하면 절반은 이해한 것이다.
- Store
- State
- Action
- Reducer
- Dispatch
큰 그림 (데이터 흐름)
텍스트로 그리면 대략 이런 흐름이다.
사용자 클릭
→ dispatch(action)
→ reducer가 현재 state와 action으로 새로운 state 계산
→ store가 상태를 바꾸고
→ 구독 중인 컴포넌트들이 새 상태로 리렌더링
[사용자 이벤트]
↓ (dispatch)
[ Action ]
↓
[ Reducer ] + [현재 State]
↓
[ 새 State in Store ]
↓
[구독한 UI가 다시 렌더링]
개념 하나씩 알아보기
1) Store
전역 상태가 저장되는 중앙 창고
앱 전체에서 단 하나만 존재하는 경우가 일반적
// 개념적으로는 이런 느낌
const store = {
state: { count: 0, user: null },
getState() {},
dispatch(action) {},
subscribe(listener) {}
};
2) State
store 안에 들어 있는 현재 상태 값
객체 한 덩어리라고 생각하면 됨:
{ count: 0, user: null, todos: \[\] } 이런 식
3) Action
무슨 일이 일어났는지를 설명하는 객체
꼭 type 필드를 가져야 함
{ type: 'counter/increment' }
{ type: 'counter/decrement' }
{ type: 'user/set', payload: { name: 'Jiyoung' } }
4) Reducer
(현재 state, action) => 새로운 state
- 순수 함수여야 함
- 인자로 받은 state를 직접 변경 X
- 항상 새로운 객체를 만들어서 반환 O
같은 입력 → 항상 같은 출력
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case 'counter/increment':
return { ...state, value: state.value + 1 };
case 'counter/decrement':
return { ...state, value: state.value - 1 };
default:
return state;
}
}
5) Dispatch
이 action을 store에 보내서 상태를 바꿔달라고 알리는 함수
store.dispatch({ type: 'counter/increment' });
바닐라 JS로 '카운터 상태관리' 예제
이 단계에서는 React 없이 순수 JS로 Redux 패턴을 흉내 내보면서 흐름을 몸에 익히는 연습이다.
아주 단순한 ‘가짜 Redux’ 구현
아래 코드는 '진짜 Redux 라이브러리'는 아니고,
개념을 이해하기 위한 아주 단순한 store 구현 예제예요.
// 1. reducer 정의
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case 'counter/increment':
return { ...state, value: state.value + 1 };
case 'counter/decrement':
return { ...state, value: state.value - 1 };
default:
return state;
}
}
// 2. createStore 함수 (미니 버전)
function createStore(reducer) {
let state = reducer(undefined, { type: '__INIT__' });
let listeners = [];
return {
getState() {
return state;
},
dispatch(action) {
state = reducer(state, action);
listeners.forEach((listener) => listener());
},
subscribe(listener) {
listeners.push(listener);
return () => {
listeners = listeners.filter((l) => l !== listener);
};
},
};
}
// 3. store 생성
const store = createStore(counterReducer);
// 4. 상태 변경되면 콘솔에 출력
store.subscribe(() => {
console.log('현재 상태:', store.getState());
});
// 5. action 보내보기
store.dispatch({ type: 'counter/increment' }); // value: 1
store.dispatch({ type: 'counter/increment' }); // value: 2
store.dispatch({ type: 'counter/decrement' }); // value: 1
여기서 한 번 체크해볼 포인트
- createStore 안에서:
- state는 어디서만 변경되는지?
- dispatch가 하는 역할은 정확히 뭔지?
- subscribe는 어떤 상황에서 유용할지?
- React라면 '상태 바뀔 때 컴포넌트 다시 그리기'에 해당
이 정도까지 이해되면:
Redux는 결국 ‘상태 + reducer 규칙 + 구독 시스템’을
깔끔하게 만들어놓은 라이브러리 정도 감이 옵니다.