참고사이트:
- 아마 이게 제일 이해하기 쉬울걸요? React + Redux 플로우의 이해
- [React-12]react-redux로 redux더 직관적이게 사용하기
- velog.io 사이트 코드 clone
React State를 Redux로 관리한다고 하면 어떻게 관점을 가지고 개발을 해야될지 미리 공부해놓으면 도움이 많이 될 것 같아서 작성하면서 공부해봅니다.
react-redux를 썼을 때의 react, redux의 장점들
1.기존의 redux를 쓰게 되면 필연적으로 react의 코드 구조에 변화가 옵니다. 이를 최대한 막아줍니다
2.redux구조를 쓰면 모듈화해서 사용하기 불편해지고 다른 모듈에서 쓰기도 까다로운데 react-redux를 쓰면 편하게 사용할 수 있습니다.
전역에서 접근하여 사용하기 때문에 App
을 Provider
로 감싸줍니다.
<Provider store={store}>
<App />
</Provider>
react에서 사용하기 위해
등이 필요합니다.
앱에서 사용되는 state는 기본적으로 전부 여기서 집중관리 됩니다.
커다란 JSON의 결정체정도의 이미지라고 생각하면 됩니다.
Store = States라고 생각하면 됩니다.
Store 및 Store에 존재하는 State는 아주 신성한 것이라고 할 수 있습니다.
React 컴포넌트같은 하등한 것(?)이 직접 접근하려고 하면 안 되는 것이죠.
직접 접근하기 위해 Action이라는 의식을 거쳐야 합니다.
이벤트 드리븐과 같은 개념입니다.
action: {
type: "액션의 종류를 한번에 식별할 수 있는 문자열 혹은 심볼",
payload: "액션의 실행에 필요한 임의의 데이터",
}
// example
export const ADD_VALUE = '@@myapp/ADD_VALUE' ;
export const addValue = amount => ({
type: ADD_VALUE,
payload: amount
});
참고
style guide(like convention, pattern) : https://redux.js.org/style-guide/style-guide#introduction
앞에 ‘Store의 문지기’라고 쓴 적이 있습니다만, 그 개념과 비슷한 역할을 하는 것이 Reducer입니다.
함수형 프로그래밍에서 Reducer라는 용어는 합성곱을 의미합니다만,
Redux에 한해서는 아래와 같이 이전 상태와 Action을 합쳐, 새로운 state를 만드는 조작을 말합니다.
reducer는 불변성으로 state를 관리한다는 것을 알아야 합니다.
대규모 개발에 Reducer를 미세하게 분할하는 경우 Redux에서 제공하는 combineReducers
함수를 사용합니다.
import { combineReducers } from 'redux';
const sessionReducer = (state = {loggedIn: false, user: null}, payload) => {
};
const timelineReducer = (state = {type: "home", statuses: []}, payload) => {
};
const notificationReducer = (state = [], payload) => {
};
export default combineReducers({
session: sessionReducer,
timeline: timelineReducer,
notification: notificationReducer,
})
위 방법처럼 명시적으로 선언할 수도 있고,
import { combineReducers } from "redux";
// imports all file except index.js
const req = require.context(
".",
true,
/^(?!.\/index)(?!.\/types.d.).*.(ts|tsx)$/
);
interface ModuleType {
[moduleName: string]: any;
}
const modules: ModuleType = {};
const regex = /.\/(.*?).ts$/;
req.keys().forEach((key) => {
const moduleName = regex.test(key) && key.match(regex)![1];
if (moduleName) {
modules[moduleName] = req(key).default;
}
});
export default combineReducers(modules);
동적으로도 생성할 수도 있습니다.
(이렇게 할 수 있는 이유는 Reducer분할에 쓰인 key가 그대로 State분할에도 쓰이고, 또한 실제로 각각의 reducer의 정의 자체도 다른 파일로 나누는 것이 일반적이기 때문입니다.)
둘다 장단이 있으니 필요한 경우에 따라 사용하면 될 것 같습니다.
reducer까지 생성을 했으면 component와 reducer간에 연결하여 사용하도록 해야합니다.
사실 React의 Component자체는 Redux의 흐름에 타는 것이 불가 합니다.
흐름에 타기 위해서는 react-redux에서 제공하는 connect
라고 불리는 함수를 이용하여 접근합니다.
mapStateToProps
라고 합니다.mapDispatchToProps
라고 합니다.react-redux에서 제공되는 connect
를 통하면 위 props를 가진 component가 반환됩니다.
export default connect(
({aCount}) => ({
value: aCount
}),
dispatch => ({ dispatchAddValue: amount => dispatch(addValue(amount)) })
)(Counter)
// redux states
interface State{
aCount: number,
bCount: number,
}
export default connect(
({aCount}: State) => ({
value: aCount
}),
dispatch => ({
dispatchAddValue: amount => dispatch(addValue(amount))
})
)(Counter)
export default connect(
({aCount, bCount}: State) => ({
value: aCount,
notValue: bCount,
}), // mapStateToProps
dispatch => ({
dispatchAddValue: amount => dispatch(addValue(amount))
}) // mapDispatchToProps
)(Counter);
/* =>
<Counter
value={aCount}
notValue={bCount}
addValue={addValue}
/> 으로 된다고 생각하시면 됩니다.
*/
dispatch를 호출하게 되면 모든 Reducer가 실행 됩니다. Reducer에 switch문으로 분기를 나눈 것은 바로 이 때문입니다.
mapDispatchToProps에서 도망치기
bindActionCreators를 통해 가져온 bind된 action creator를 props로 넘겨서 사용하여 간단하게 쓸 수 있습니다.
export default connect(
({aCount}: State) => ({ value: aCount }),
dispatch => bindActionCreators({ addValue }, dispatch)
)(Counter)
bindActionCreators는 생략해서도 사용가능합니다.
export default connect(
({aCount}: State) => ({ value: aCount }),
{ addValue }
)(Counter)
실은 React+Redux만으로는 아직 불편한 경우가 많습니다.
Reducer 안에 부작용이 생길 처리를 써선 안 된다
즉, 아래와 같은 경우에는 reducer에서 처리할 수 없습니다.
- 같은 입력에 대해 확률적으로 다른 결과가 나오는 처리
- 지연처리
- HTTP 리퀘스트 처리
라는 원칙 때문입니다.
보통 비동기처리를 할 경우에 react-redux만을 사용했을 땐 불편함을 느낄 수 있습니다.(안티 패턴을 통해 구현을 해야합니다.)
Saga는 제너레이터 함수이기 때문에, 비동기처리를 간단히 다룰 수 있습니다.
yield take(ACTION_TYPE)
으로 지정한 action의 발생을 감시한다yield put(action)
의 결과를 다른 action으로 내보낼 수 있다기본적으론 이런 것들이 가능합니다. 내보낸 action은 Reducer를 향하게도 할 수 있고 자기 자신의 Saga에게 다시 올 수도 있고 자기 외의 다른 Saga에 보낼 수 있을지도 모릅니다.
redux-thunk도 비슷한 의도로 생겨난 것입니다.
saga는 다음 post에서 자세하게 이해해보도록 하겠습니다.
(솔직히 redux도 완벽하게 이해는 못했습니당...)