본문 바로가기

FrontEnd+

[Redux] 리액트 앱에서 Redux로 상태관리 하기

 

Redux 공식 홈페이지에서는 Redux를 자바스크립트 앱을 위한 '예측 가능한' 컨테이너라고 소개한다. 지나간 상태도 관리하기 버거운데 예측까지 가능하게 해 준다니 상당히 매력적인 문구이다,,, 리액트 앱에서 Redux를 적용해 보면서 정말로 상태 관리하기가 더 수월해지는지 경험해보자!

 

리액트 공식 홈페이지(https://redux.js.org/)

 

 

 

Redux 기초개념

egghead는 자바스크립트 개발자를 위한 인강 사이트이다. Dan Abramov, Kent C. Dodds 등 유명한 자바스크립트 개발자의 강의를 로그인 하지 않고도 무료로 들을 수 있다.(일부 유료) 

리덕스를 만든 Dan Abramov가 직접 리덕스를 소개하는 이 강의 각 강의가 1~3분 내로 호흡이 짧고, 나같은 리덕스 입문자가 이해하기도 쉽게 설명해줘서 리덕스의 주요 개념을 이해하는데 큰 도움이 되었다. (썬 추천 고마와용🤍)

만든 사람이 알려주니까 아주 좋네요 👍

Store 란?

Store는 자바스크립트 객체이다. 객체 하나에 앱의 모든 상태가 담겨있다. 이를 상태 트리(state tree)라고도 한다. Store는 변경 추적에 용이하게 하기 위해 불변 객체로 관리한다. 직접 수정할 수 없는 읽기 전용 객체로, 오직 Reducer 함수에 의해서만 변경할 수 있다.

const counter = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
} 

const { createStore } = Redux;
const store = createStore(counter);

const render = () => {
  document.body.innerText = store.getState();
};

store.subscribe(render);
render();

document.addEventListener('click', () => {
  store.dispatch({ type: 'INCREMENT' });
});
const counter = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default: 
      return state;
  }
}

const createStore = (reducer) => {
  let state;
  let listeners = [];
  
  const getState = () => state;
  
  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach(listener => listener());
  };
  
  const subscribe = (listener) => {
    listeners.push(listener);
    return () => {
      listeners = listeners.filter(l => l !== listener);
    };
  };
  
  dispatch({});
  
  return { getState, dispatch, subscribe };
};

const store = createStore(counter);

const render = () => {
  document.body.innerText = store.getState();
};

store.subscribe(render);
render();

document.addEventListener('click', () => {
  store.dispatch({ type: 'INCREMENT' });
});

Action 이란?

Action도 자바스크립트 객체로, 무슨 변경이 일어났는지를 나타낸다. 어떤 사용자 인터랙션 일어났는지, 어떤 네트워크 요청이 일어났는지 등을 구분할 수 있도록 해준다.

어떻게 Action을 작성할지는 자유지만 반드시 type 프로퍼티를 가져야한다. type 프로퍼티 값은 undefined이면 안되고, 직렬화 가능한 문자열을 사용하는 것을 권장한다. 컴포넌트에서 이벤트가 발생해서 상태를 변경해야 하면 Action을 보내서(Dispatch) Reducer함수를 실행하게 한다.

Reducer 란?

Reducer에서는 이전 State와 Action을 참고해서 새로운 State를 만든다. 카운터 앱을 예로 들면 { type: 'INCREMENT' }라는 Action이 발생했으면 Reducer 함수는 state + 1 을 반환한다.

Reducer는 순수함수이어야 한다. 즉, 인자를 수정하지 않고 새로운 값을 반환해야 하고, 다른 사이드 이펙트도 없어야 한다. 순수 함수로 작성하는 것은 상태를 예측 가능하도록 관리하는데 중요한 역할을 한다.

단, 변경이 일어나지 않은 다른 프로퍼티는 얕은 복사를 해서 새로운 객체를 반환하기 때문에 새로운 객체를 계속해서 만들더라도 리덕스는 빠른 속도를 낼 수 있다.

보통 정의하지 않은 action이 들어왔을 때를 처리하기 위해 default로 기존의 state를 그대로 반환하도록 한다.

최초 실행 시 state가 undefined인 경우, 즉, typeof state === "undefined" 인 경우에는 초기 설정값을 반환하도록 한다. 인자의 기본값 설정 문법(ES6)을 사용해서 아래와 같이 더 깔끔하게 작성할 수 있다.

const reducer = (state = initialState, action) => {} 

Store 더 알아보기

Store는 redux에서 가져온 createStore 메서드로 생성할 수 있다. Action이 발생했을 때, Store를 어떤 Reducer로 업데이트할 것인지 명시하기 위해 createStore의 인자로 해당 Reducer를 넣어주어야 한다.

const counter = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
} 

const { createStore } = Redux;
const store = createStore(counter);

Store에는 3가지 중요한 메서드가 있다. 첫 번째 메서드는 가장 많이 사용할 dispatch() 메서드이다. dispatch는 앱에서 변경이 발생했을 때 Action을 보내줄 때 사용한다. 두 번째 메서드는 현재 상태를 반환하는 getState() 메서드이다. 세 번째 subscribe() 메서드는 Action을 dispatch 하고 나서 자동으로 실행할 함수를 등록한다. 만약 subscribe에만 render함수를 등록해주고 앱 실행 시 따로 render를 호출하지 않으면, 주면 초기 상태는 렌더 되지 않는다.

const Counter = ({
  value,
  onIncrement,
  onDecrement
}) => (
  <div>
    <h1>{value}</h1>
    <button onClick={onIncrement}>+</button>
    <button onClick={onDecrement}>-</button>
  </div>
);

const render = () => {
  ReactDOM.render(
    <Counter
      value={store.getState()}
      onIncrement={() =>
        store.dispatch({
          type: 'INCREMENT'           
        })            
      }
      onDecrement={() =>
        store.dispatch({
          type: 'DECREMENT'           
        })            
      }
    />,
    document.getElementById('root')
  );
};

store.subscribe(render);
render();

 

 

 

Redux 모듈 관리하기

redux.js.org/faq/code-structure

리덕스의 Github 계정(reduxjs/redux)에서는 다음과 같이 store, actions, reducers, middleware를 각각 별도의 디렉토리에서 관리하고 있다. (2019년 1월 마지막 업데이트

examples/realworld/src

하지만 대부분의 경우에 Action과 Reducer 함수는 서로 짝지어 사용된다고 한다. 따라서 이렇게 하는 대신에 연관있는 Action 객체들과 Reducer 함수를 한 파일 내에 작성하면, 리덕스의 동작 흐름을 파악하기도, 코드를 관리하기도 더 수월하다.

1. reducer 함수를 export default 한다.
2. action creator 함수들을 export 한다.
3. action type은 app/reducer/ACTION_TYPE 형태로 작성한다.
(외부 라이브러리로서 사용될 경우 또는 외부 라이브러리가 필요로 할 경우에는 UPPER_SNAKE_CASE 로만 작성해도 괜찮다.)

이 패턴의 제안자 Erik Rasmussn은 리덕스의 덕스를 따서 덕스 패턴이라고 부른다. (스펠링은 Ducks🐥)

 

 

 

참고자료

- 6. 리덕스 개발자도구 적용하기
- 
리덕스 미들웨어, 그리고 비동기 작업
- Redux (3) 리덕스를 리액트와 함께 사용하기