React-Redux

Seo·2020년 11월 9일
0

Front-End

목록 보기
18/21
post-thumbnail

React-Redux

참고사이트:

React State를 Redux로 관리한다고 하면 어떻게 관점을 가지고 개발을 해야될지 미리 공부해놓으면 도움이 많이 될 것 같아서 작성하면서 공부해봅니다.

react-redux를 썼을 때의 react, redux의 장점들
1.기존의 redux를 쓰게 되면 필연적으로 react의 코드 구조에 변화가 옵니다. 이를 최대한 막아줍니다
2.redux구조를 쓰면 모듈화해서 사용하기 불편해지고 다른 모듈에서 쓰기도 까다로운데 react-redux를 쓰면 편하게 사용할 수 있습니다.

구성 요소

  1. provider, node_moduels
  2. store
  3. action(action creator)
  4. reducer
  5. dispatch

0. Provider

전역에서 접근하여 사용하기 때문에 AppProvider로 감싸줍니다.

<Provider store={store}>
  <App />
</Provider>

react에서 사용하기 위해

  • react-redux
  • redux-actions
  • typesafe-actions (typescript에서 사용)
  • immer (불변성 관리 편하게)
  • chrome redux 개발자도구

등이 필요합니다.

1. Store

앱에서 사용되는 state는 기본적으로 전부 여기서 집중관리 됩니다.
커다란 JSON의 결정체정도의 이미지라고 생각하면 됩니다.

Store = States라고 생각하면 됩니다.

2. Action 및 Action Creator

Store 및 Store에 존재하는 State는 아주 신성한 것이라고 할 수 있습니다.
React 컴포넌트같은 하등한 것(?)이 직접 접근하려고 하면 안 되는 것이죠.

직접 접근하기 위해 Action이라는 의식을 거쳐야 합니다.
이벤트 드리븐과 같은 개념입니다.

  1. Store에 대해 뭔가 하고 싶은 경우엔 Action 을 발행합니다.
  2. Store의 문지기가 Action의 발생을 감지하면, State가 경신됩니다.
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

3. Reducer

앞에 ‘Store의 문지기’라고 쓴 적이 있습니다만, 그 개념과 비슷한 역할을 하는 것이 Reducer입니다.

함수형 프로그래밍에서 Reducer라는 용어는 합성곱을 의미합니다만,

Redux에 한해서는 아래와 같이 이전 상태와 Action을 합쳐, 새로운 state를 만드는 조작을 말합니다.

  1. 초기상태는 Reducer의 디폴트 인수에서 정의된다
  2. 상태가 변할 때 전해진 state는 그 자체의 값으로 대체 되는 것이 아니라, 새로운 것이 합성되는 것처럼 쓰여진다.(immutability)

reducer는 불변성으로 state를 관리한다는 것을 알아야 합니다.

combineReducers

대규모 개발에 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의 정의 자체도 다른 파일로 나누는 것이 일반적이기 때문입니다.)

둘다 장단이 있으니 필요한 경우에 따라 사용하면 될 것 같습니다.

Connect

reducer까지 생성을 했으면 component와 reducer간에 연결하여 사용하도록 해야합니다.

사실 React의 Component자체는 Redux의 흐름에 타는 것이 불가 합니다.

흐름에 타기 위해서는 react-redux에서 제공하는 connect라고 불리는 함수를 이용하여 접근합니다.

  • state to props: redux에서 관리되고 있는 state를 props를 통해 받기 위해 mapping 합니다. 이러한 함수를 mapStateToProps라고 합니다.
  • dispatch to props: action을 알리는 함수인 dispatch를 props를 통해 받기 위해 mapping 합니다. 이러한 함수를 mapDispatchToProps라고 합니다.

react-redux에서 제공되는 connect를 통하면 위 props를 가진 component가 반환됩니다.

export default connect(
  ({aCount}) => ({ 
    value: aCount
  }),
  dispatch => ({ dispatchAddValue: amount => dispatch(addValue(amount)) })
)(Counter)

mapStateToProps, mapDispatchToProps

  • mapStateToProps: react-redux에서 관리되는 state(전체)를 가져오기 때문에 해당 component에서 필요한 state만을 추출해서 사용합니다.
  • mapDispatchToProps: Action Creator에서 action을 만든다고 해도, 그것으론 아무 일도 일어나지 않습니다. Reducer를 향해 통지를 할 수 있게 만들기 위해서는 만든 action을 dispatch라는 함수에 넘겨줘야만 합니다.
// 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문으로 분기를 나눈 것은 바로 이 때문입니다.

bindActionCreators

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)

Redux Saga

실은 React+Redux만으로는 아직 불편한 경우가 많습니다.

Reducer 안에 부작용이 생길 처리를 써선 안 된다
즉, 아래와 같은 경우에는 reducer에서 처리할 수 없습니다.

  • 같은 입력에 대해 확률적으로 다른 결과가 나오는 처리
  • 지연처리
  • HTTP 리퀘스트 처리

라는 원칙 때문입니다.

보통 비동기처리를 할 경우에 react-redux만을 사용했을 땐 불편함을 느낄 수 있습니다.(안티 패턴을 통해 구현을 해야합니다.)

Saga는 제너레이터 함수이기 때문에, 비동기처리를 간단히 다룰 수 있습니다.

  • yield take(ACTION_TYPE)으로 지정한 action의 발생을 감시한다
  • 가져온 action을 구워먹고 삶아먹고 마음대로 할 수 있다
  • yield put(action)의 결과를 다른 action으로 내보낼 수 있다

기본적으론 이런 것들이 가능합니다. 내보낸 action은 Reducer를 향하게도 할 수 있고 자기 자신의 Saga에게 다시 올 수도 있고 자기 외의 다른 Saga에 보낼 수 있을지도 모릅니다.

redux-thunk도 비슷한 의도로 생겨난 것입니다.
saga는 다음 post에서 자세하게 이해해보도록 하겠습니다.
(솔직히 redux도 완벽하게 이해는 못했습니당...)

profile
개발관심자

0개의 댓글