Redux 개요
Redux는 자바스크립트 애플리케이션을 위한 예측 가능한 상태 관리 컨테이너입니다. 단일 상태 트리와 불변성 원칙을 기반으로 하여, 애플리케이션의 상태 변화를 명확하고 예측 가능하게 만듭니다.
Action: 상태 변화의 신호
Action은 데이터가 애플리케이션에서 스토어로 전달되는 메시지입니다. 이는 단순한 자바스크립트 객체이며, 반드시 type 필드를 포함해야 합니다. 일반적으로 문자열 상수로 정의되며, 액션 생성 함수를 통해 쉽게 생성할 수 있습니다.
const increment = (payload) => ({
type: 'INCREMENT',
payload
});
액션은 상태를 변경하는 행위 자체를 설명할 뿐, 어떻게 변경할지는 지정하지 않습니다.
Reducer: 상태 변화 로직
Reducer는 이전 상태와 액션을 받아 새로운 상태를 반환하는 순수 함수입니다. 항상 동일한 입력에 대해 동일한 출력을 보장해야 하며, 외부 상태나 부작용을 일으켜서는 안 됩니다.
const counterReducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + action.payload;
case 'DECREMENT':
return state - action.payload;
default:
return state;
}
};
기본적으로 상태는 수정하지 않고, 새 객체를 반환해야 합니다. 객체 병합 시 Object.assign() 또는 객체 전개 연산자({...state, ...newData})를 활용합니다.
Store: 상태 저장소
스토어는 액션, 리듀서, 상태를 연결하는 중심 역할을 합니다. 단 하나의 스토어만 존재하며, 모든 상태가 이곳에 집중됩니다.
import { createStore } from 'redux';
import rootReducer from './reducers';
const store = createStore(rootReducer);
스토어는 다음 기능을 제공합니다:
getState(): 현재 상태 조회dispatch(action): 액션 전달subscribe(listener): 상태 변화 감시 등록
상태 조합: combineReducers
복잡한 상태 구조를 관리하기 위해 여러 리듀서를 결합할 수 있습니다. combineReducers()는 각 리듀서가 자신의 상태 부분을 처리하도록 분리된 구조를 만들어 줍니다.
import { combineReducers } from 'redux';
import userReducer from './userReducer';
import todoReducer from './todoReducer';
const rootReducer = combineReducers({
user: userReducer,
todos: todoReducer
});
export default rootReducer;
데이터 흐름: 단방향
- 액션 발생:
store.dispatch(action)호출 - 리듀서 실행: 현재 상태와 액션을 받아 새 상태 계산
- 상태 통합: 여러 리듀서 결과를 하나의 상태 트리로 합침
- 구독 알림: 구독된 리스너들이 새로운 상태를 확인
React와 함께 사용하기
Redux는 React에 특화되지 않으며, 다양한 프레임워크와 호환됩니다. 하지만 특히 컴포넌트의 상태를 순수 함수로 표현하는 방식과 잘 맞습니다.
Container vs Presentational 컴포넌트
UI 로직과 상태 로직을 분리하는 것이 중요합니다. 대부분의 컴포넌트는 단순한 표시 기능만 수행하며, 상태 관리는 컨테이너 컴포넌트에서 담당합니다.
connect()를 이용한 연결
connect()는 리덕스 스토어의 상태를 컴포넌트의 프로퍼티로 매핑하고, 액션을 디스패치할 수 있는 메서드를 제공합니다.
import { connect } from 'react-redux';
const mapStateToProps = (state) => ({
count: state.count
});
const mapDispatchToProps = (dispatch) => ({
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' })
});
const CounterContainer = connect(
mapStateToProps,
mapDispatchToProps
)(CounterUI); // CounterUI: UI 컴포넌트
Provider로 스토어 공유
모든 컨테이너 컴포넌트가 스토어에 접근할 수 있도록, 루트 컴포넌트를 <Provider>로 감싸면 됩니다.
import { Provider } from 'react-redux';
import { createStore } from 'redux';
const store = createStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);