2020.07.14(화) Sprint 9. Redux

Park, Jinyong·2020년 7월 14일
0

Today I Learned

Redux

react에서 state와 props를 관리할 때는 단방향 흐름을 가지고 있다. 즉 최상위 컴포넌트에서 가장 깊은 곳까지 state와 state handler 함수를 보낼 일이 많다. 최상위 컴포넌트가 관리하는 state가 많아질수록 코드는 점점 무거워지고 유지보수가 어려워진다. Redux는 이를 해결하기 위한 javascript 상태 관리 라이브러리이다. React에서 뿐만 아니라 상태를 관리해야할 필요가 있는 모든 곳에 적용할 수 있다.

Redux의 장점

  • 앱을 특정 상태로 한 번에 전환하는 것이 가능하다.
  • 어떠한 컴포넌트도 어떤 상태든 접근이 가능하다.
  • Serialize 후 서버 등으로 전송하기 쉽다.
  • 상태의 이력을 관리하기에 용이하다.
  • 상태와 UI를 분리해서 테스트할 수 있다.
import { createStore } from 'redux';

// reducer: 현재 상태를 다음 상태로 변경
// |(state, action) => state| 형태의 순수 함수
// 순수 함수이므로 state를 변경하면 안된다.
function counter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state
  }
}

// store: 앱의 상태를 보관하는 redux 스토어를 생성
// reducer를 인수로 전달 받는다.
// API { subscribe, dispatch, getState }
let store = createStore(counter);

// state 변경을 감지한다.
store.subscribe(() => console.log(store.getState()));

// action을 보낸다.
store.dispatch({ type: 'INCREMENT' });

action

앱에서 스토어로 보내는 데이터 묶음. store.dispatch를 통해 이들을 보낼 수 있다.

Action으로 다음 상태를 정의할 수 있다. Javascript의 객체로 작성하며 type은 반드시 포함해야하고 데이터의 이름과 타입은 임의로 작성할 수 있다.

action 객체 구조
const ACTION_TYPE = 'ACTION_TYPE';
{
  type: ACTION_TYPE,
  data: '데이터'
}

data는 임의의 이름을 가질 수 있고, 모든 타입이 가능하다.

action 생산자

액션을 생성하는 함수

function createAction(data) {
  return { type: ACTION_TYPE, data };
}

createActiondata는 임의의 이름을 가질 수 있다.

action 보내기
dispatch(createAction(data));
const boundAction = (data) => dispatch(createAction(data));

boundAction(data);

reducer

이전 상태와 action을 이용해 다음 상태를 반환한다.

Reducer는 pure function이어야 한다. 동일한 입력에는 항상 동일한 출력을 반환해야 한다. 그리고 외부 스코프의 상태를 변경하면 안된다. 참조 타입의 매개변수가 가진 값을 변경하면 안된다. Reducer는 이전 state와 action을 매개변수로 가지는데 reducer가 순수 함수일 경우 action을 어떻게 설정하느냐에 따라 결과를 쉽게 예상할 수 있다.

reducer 내에서 하지 말아야 할 것들
  • 인수들을 변경하기
  • API 호출이나 라우팅 전환 같은 side effect 일으키기
  • Date.now()Math.random() 같이 순수하지 않은 함수를 호출하기
reducer 정의
function reducer(state = {}, action) {
  switch(action.type) {
    case ACTION_TYPE:
      return { ...state, data: action.data };
    default:
      return state;
  }
}
reducers
import { combineReducers } from 'redux';

const reducer = combineReducer({
  reducer1,
  reducer2
});

export default reducers;

store

앱의 상태를 저장하고 있다. Redux 앱에서 단 하나의 스토어만 가질 수 있다.

  • getState()를 통해 상태에 접근하게 한다.
  • dispatch(action)를 통해 상태를 수정할 수 있게 한다.
  • subscribe(listener)를 통해 리스너에 등록한다.
store 생성
import { createStore } from 'redux';
import reducers from './reducers';

const store = createStore(reducer);

export default store;
store 이용
import store from './store';
import { reducer1, reducer2 } from './reducers';

// 초기 상태를 기록한다.
console.log(store.getState());

// 상태 바뀔 때마다 기록한다.
store.subscribe(() => console.log(store.getState()));

// 상태를 수정한다.
store.dispatch(reducer1(action));
store.dispatch(reducer2(action));

presentational component

보여지는 부분을 담당하는 컴포넌트이다. presention에 관련된 state만 가지고 있다.

container component

어떠한 작업을 수행하는 컴포넌트이다. 데이터를 관리한다.

mapStateToProps

store의 state를 presentational 컴포넌트의 props로 mapping한다.

import { connect } from 'react-redux';
import Component from '../components/Component';

const mapStateToProps = (state) => ({
  props: state.reducers1.data;
});

export default connect(mapStateToProps)(Component);

저번 스프린트에서 react의 lifting state up을 구현할 때 발생할 수 있는 불편함과 문제점에 대해서 얘기를 했다. 이번 스프린트에서는 이런 어려움을 해소하기 위한 javascript 라이브러리인 redux에 대해서 공부하고 recast.ly에 적용해보았다. 스프린트를 진행함에 따라 설명이 점점 불친절해지는데 의도는 결국 공식 문서를 전부 읽어보라는 얘기다. 이번 redux는 그나마 공식 문서가 잘 정리되어 있어서 읽는데 어려움을 없었지만, 처음 보는 용어들이 우후죽순으로 튀어나와 개념을 정리하고 적용하는데 어려움을 겪었다. 하지만 이번 스프린트의 의도도 그렇고 어차피 처음부터 설계하는 일은 거의 없고 선임 개발자가 이미 작성한 코드에서 유지보수를 하거나 일부 기능을 추가하는 경우가 대부분이므로 개념만 정확히 알고 넘어가면 괜찮다고 한다. 그래도 많은 기업에서 적용 중인 기술이기도 하고 react에서만 적용할 수 있는 기술이 아니므로 처음부터 작성할 수 있는 기회를 마련해보아야겠다.

0개의 댓글