redux는 react에서 많이 사용되는 상태관리를 위한 라이브러리라고 볼 수 있다. 즉 리액트 이외에서도 사용할 수 있지만 redux와 react-redux의 npm 다운로드를 비교했을때 아마도 react의 상태관리를 위한 도구로 가장 많이 사용되는 것으로 추정된다. 그런데 처음 진입장벽이 높은 편이라 공부하는데 시간이 오래걸리고 코드의 난이도로 어려움을 겪게된다. 그래서 이 글은 가장 초보자가 Redux를 가장 빠르게 핵심개념을 이해할 수 있도록 작성하였다.
기본적인 상태관리의 흐름은 context api와 유사하다. 다만 사용되는 용어, 구현방과 작동방식에 있어서 차이는 있으나 기본적으로 어플리케이션의 상태를 객체로 관리하고 이를 구독하는 방식으로 움직인다는 것은 동일하다.
redux에서 사용되는 용어는 스토어, 액션, 디스패치, 리듀서가 있다.
글로 풀어보면 스토어는 상태를 가지고 있으며 컴퍼넌트는 디스패치 함수를 통해 액션을 리듀서로 전달한다. 그리고 상태는 리듀서에 의해서만 변경이 가능하고 컴퍼넌트는 이러한 상태를 구독하게 되는 것이다. 좀 더 나아가면 미들웨어라는 내용이 추가되는데 미들웨어는 리듀서와 액션사이에서 어떤 역할을 수행하게 되고 비동기 작업등을 처리하게 된다.
그림으로 보면 아래와 같다.
출처; https://stackoverflow.com/questions/51610872/storing-to-redux-on-page-load/51611975
우선 create-react-app 으로 프로젝트를 생성하고 react-redux를 추가한다.
// 프로젝트 생성
npx create-react-app hello-redux
// react-redux 설치
cd hello-redux
npm i redux react-redux
카운터 어플리케이션에 필요한 초기 상태값을 정의해 둔다.
export const INIT_COUNT_STATE = {
value: 0
};
카운터의 상태값이 변경되는 action을 정의한다. 기본적인 카운터 어플리케이션에서는 +와 -버튼에 의해 상태가 변경될 것이다.
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'
export function increment() {
return {
type: INCREMENT
};
}
export function decrement() {
return {
type: DECREMENT
};
}
앞서 정의한 상태값으로 초기화하고 액션에 의해 상태값을 변화시키는 내용이 작성된다.
import { INIT_COUNT_STATUS } from '../status/count'
import { INCREMENT, DECREMENT } from '../actions/count';
import { combineReducers } from 'redux';
function counter (state = INIT_COUNT_STATUS, action) {
switch(action.type) {
case INCREMENT:
return {
...state,
value: state.value + 1
}
case DECREMENT:
return {
...state,
value: state.value - 1
}
default:
return state
}
}
const counterReducer = combineReducers({ counter });
export default counterReducer
store구성은 완료되었고 이를 컴퍼넌트에서 사용해야한다. context api와 마찬가지로 provider이 존재해야 한다. 그리고 상태값을 구독하는 컴퍼넌트에서는 connect 라는 함수를 사용하게 된다.
* provider 등록 (index.js)
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from './App';
import CountReducer from './redux/reducer/count';
const store = createStore(CountReducer);
ReactDOM.render(
<Provider store = { store }>
<App />
</Provider>,
document.getElementById('root')
);
import React from 'react';
import { connect } from 'react-redux'
function Hello(state) {
return (
<div className="hello">
{state.value}
</div>
);
}
const mapStateToProps = (state) => {
return {
value: state.counter.value
}
}
export default connect(
mapStateToProps
)(Hello);
import React from 'react';
import { connect } from 'react-redux'
import { increment, decrement } from '../redux/actions/count';
function Button(store) {
return (
<div className="button-group">
<button onClick={store.onIncrement}>+</button>
<button onClick={store.onDecrement}>-</button>
</div>
);
}
const mapDispatchToProps = (dispatch) => {
return {
onIncrement: () => dispatch(increment()),
onDecrement: () => dispatch(decrement())
}
}
export default connect(
null,
mapDispatchToProps
)(Button);
위의 내용은 가장 초보자를 위해 작성하였으며 튜토리얼보다 쉬운 수준이다. 처음 배울때 난해했던 개념들을 배제하여 가능한 빠르게 이해할 수 있도록 작성하였다. 그럼에도 불구하고 알아야할 내용이나 해야할 것들이 다른 상태관리도구에 비해 훨씬 많다. 그리고 여기에 어떤 미들웨어를 붙이고 발전시켜가는 과정은 더 어렵다. 하지만 구현해 놓고 나면 다양한 것들을 할 수 있고 특히 과거의 상태와 현재의 상태를 비교할 수 있기때문에 강력한 도구이며 어플리케이션이 복잡해짐에 따라 많은 도움을 받을 수 있다. mobx와 비교하면 성능상의 이점도 크다고 한다.
많은 곳에서 사용하고 있는 상태관리 도구로서 알아두어야 할 필요성은 있지만, 쉽고 좋은 대안들이 많이 있기 때문에 새로 개발하고자 한다면 굳이 사용해야하는 것인지는 생각해봐야한다. 지금까지 리액트 경험은 얼마 없고 context api와 mobx로 큰 문제 없이 개발을 했으나 얼마나 복잡한 어플리케이션에 유용한지는 모르겠으나 아직까지는 경험해보지 못했다.
참고로 위에 잠깐 언급된 미들웨어는 직접 만들어 구성하거나 별도의 라이브러리를 선택하는 과정을 거치게 된다.
대표적으로 비동기 처리를 위한 redux-saga, redux-thunk, redux-pack, 로그관리를 위한 redux-loger 등이 있다. 이에 대한 추가의 학습이 필요하면 아래 링크를 참조하면 좋을 것 같다.