Redux

할로윈기간간 이태원사태가 있었다. 참 애매하게 생각했다. 세월호랑은 다르게 놀러갔다 압사한걸 나라에서 왜 책임지지라고 말하고 다녔으나, 실제로 세월호에 연관된 가족들이 슬펐던 것처럼. 내 가족이 아니면 내 친구가 더 멀리가서 내가 아는사람이 비슷한 일을 당했더라도 기분은 좋지 않을거 같다. 실제로 친구가 가자고 했으나, 선약이 있어 못갔고, 친구역시 이태원에 그시각에 있었다고 했다. 내가 겪을 수 도 있었을 거란 생각이 들었다.

삼가 고인에 명복을 빕니다.

0. 필요성

처음 udemy에서 React관련 강의를 들으며, props를 배울 때 뭐이런 쓸데없는 전달을 하지라는 생각을 했었다. 관련 내용에 대해 매번 의문이 들었고, Redux를 배우기 전 cs를 시작하게 되어 js부터 css, react를 순차적으로 배우기 시작했고, redux에 다가와서야 그 이유를 알게 되었다.

소문상의 cs의 양대축인 홍샘과 아샘에게 문의 하였고, 원래 facebook에서는 react를 배포할때는 redux가 없었고, redux는 store모형으로 관리하는 것으로 보아, 이전 재훈이가 만들어준 store형태 구조가 생각났다. 심지어 react가 나조차도 ‘왜이렇게 만든거야 장난하나’ 라고 생각할 정도로 비효율적이라고 생각한 이 구조를 개선할 방안으로 redux(아마 facebook측에서라기보다는 react를 개발하는 개발자들이 사용한게 이슈가된거같은 추론)를 만든사람을 영입했다고 답변해주었다.

역시는 역시

얼마나 편한건지 알아보자.

1. before Redux

React는 상태관리를 위한 라이브러리는 아니다. 컴포넌트들이 많아지고 복잡해지면 점점 loose coupled(느슨한 결합)에서 점점 멀어지게된다. 기능이 많아지고, 프로젝트가 거대해질수록 구조는 점점 복잡해지고, 데이터를 주고받는 저장소가 없다면 결국은 side effect를 벗어날 수 없다.

좋은 프로그램이 되는 조건중 하나인 데이터의 무결성을 위해서라도 React가 아무리 편한 방법이라 할지라도 결국은 그 편함으로 인해 생기는 또다른 문제점들이 생긴다.

React는 데이터를 각 컴포넌트에서 내가 관리하는게 아니라 React가 관리하므로 의도하지 못한 이슈가 발생할 수 있고, 이를 막기위한 현인들의 방법론적 연구가 있었던걸로 보인다.

데이터 무결성: 데이터의 정확성을 보장하기 위해 데이터의 변경이나 수정 시 제한을 두어 안정성을 저해하는 요소를 막고 데이터 상태들을 항상 옳게 유지하는 것.
Single source of truth(신뢰할 수 있는 단일 출처): 동일한 데이터는 항상 같은 곳에서 데이터를 가지고 온다.

상태 관리를 위한 각종 툴로는 React Context, Redux, MobX 등이 있는데, 이들은 전역상태의 저장소(store)를 제공한다.

허나 무조건 상태관리 툴은 옳은 것이 아니라고 Redux의 창시자인 Dan Abramov도 ‘You might not need redux’라는 아티클을 통해 ‘React로 사고하기’만 이해하더라도 대부분의 문제를 해결할 수 있으니, 장단점을 알고 사용해야 한다.

1-1. Props Drilling

출처: Jeongki.dev: 데브코스-51일차-til-react https://qph.fs.quoracdn.net/main-qimg-212a32ca0d74c300fdeb2c6a6e751769

export default Content = ({ value, ... }) => {
  return (
    <section>
      <Content1 value={value} ... />
    <section>
  )
};

export default Content1 = ({ value, ... }) => {
  return (
    <section>
      <Content2 value={value} ... />
    <section>
  )
};

export default Content2 = ({ value, ... }) => {
  return (
    <section>
      <Content... value={value} ... />
    <section>
  )
};

...

위와같이 depth가 늘어간다면 value를 찾기위해 상위 컴포넌트로 한없이 올라가야하고, 수정하려면 상위컴포넌트를 타고가며 고쳐줘야한다. 인텔리J처럼 연결된 모든 값을 한번에 처리할 수 있는 기능(AndroidStudio IDE기반이 Intelli J)이 없을거 같지만, 있다 하더라도 관련내용들이 React에서는 누가 주는지 무슨값을 주는지 알 수 없는 상태에서 value라는 텍스트로만 검색한다면 분명히 모든 value라고 정의되있는 text는 전부 변경될것이기에 난감한 일이 아닐 수 없다.

//index.js
...
import { legacy_createStore as createStore } from 'redux';
import { Provider } from 'react-redux';
import { reducer } from './reducer.js';

const store = createStore(reducer);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

//==============================================================
//App.js
...
import { useSelector, useDispatch } from 'react-redux';
...
export default App = () => {
  const value = useSelector(state => state);
  ...
  return (
    <Container>
      <Content />
    </Container>
  )
}

...

export default Container...LastChild = () => {
  const dispatch = useDispatch();
  const handleValue = () => {
    dispatch({ type: '명칭' })
  };
  ...
  return (
    <section>
      <span>{`${value} 값을 전달받음`}</span>
      <input type="button" onClick={handleValue}> 증가</input>
    </section>
  );
}

//==============================================================
//reducer.js
export const reducer = (initialState = 초기화값, action) => {
  let newState = initialState;
  switch (action.type) {
    case '명칭':
      return 돌려줄 액션 ex => newState++;
    ...
    default:
      return initialState;
  }
};

상당히 간편해진 것을 알 수 있고, 구조화로 인해 유지보수 또한 용이하게 할 수 있다. 이런 편리한건 역시 알아야한다.

2. Redux

React는 state와 props를 이용한 component 단위의 개발 architecture이고, redux는 component와 state를 분리하는 pattern이다. redux는 react에 종속된게 아닌, 독립적인 library이다.

          1
          /\
        2   3
        /\  /\
       4  5 6 7

         STORE

React에서는 최상위인 1컴포넌트에서 4와 7로 top-down방식으로 데이터를 흘려줘야한다.

4와 7을 위한 데이터는 2와 3을 통해 전달되어 데이터가 변경될 때 마다 2와 3또한 리렌더링되는 단점이 있다.

그러나 STORE를 통해 4와 7에 담긴 데이터를 수정할 시에는 그런 쓸모없는 리랜더링은 발생하지 않으므로 효율적이다.

ㅏㅡㅡ> Dispatch ㅡㅡ> Store{Reducer{} {count:2}}ㅡㅓ
ㅣ                                                 ㅣ
UI: count:1 + - <ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅓ

# 동작 흐름
UI: count:1 -> +버튼 클릭 -> Action{type:Increase} -> Dispatch{Action} ->
-> Store{Reducer{Action} => {count:2}} -> UI: count:2 ->
-> -버튼 클릭 -> Action{type:Decrease} -> Dispatch{Action} </br>
-> Store{Reducer{Action} => {count:1}} -> UI: count:1

flow

  1. 상태변경 이벤트 발생, 변경될 상태 정보 Action 객체 생성
  2. Action객체 Dispatch함수 인자로 전달
  3. Dispatch함수 Action객체를 Reducer함수로 전달
  4. Reducer함수 Action객체 값 확인(상시 대기중인듯), 그 값에 따라 전역상태 저장소 Store 상태 변경
  5. 상태 변경 후 화면 리렌더링 UIevent -> Action -> Dispatch -> Reducer -> Store -> UIevent: Action은 단방

2-1. Store

상태가 관리되는 오직 하나뿐인 저장소이다. Redux앱의 state가 저장되어있다.

2-2. Reducer

const option = "";

// Reducer를 생성할 때에는 초기 상태를 인자로 요구합니다.
// 값이 일정하게 예측되는 순수함수이어야함.
const optionReducer = (state = option, action) => {
  switch (action.type) {
    case GlobalStore.OPT_EMP:
      return state + "직장인";
    case GlobalStore.OPT_100:
      return state + "무직";
    case GlobalStore.OPT_FIRST:
      return state + "부모";
    case GlobalStore.OPT_NONE:
      return action.payload;
    default:
      return state;
  }
};
// Reducer가 리턴하는 값이 새로운 상태가 됩니다.

Reducer가 n개일때

import { combineReducers } from "redux";

const rootReducer = combineReducers({
  counterReducer,
  optionReducer,
  ...nRducer,
});

2-3. Action

이벤트에 따른 어떤 액션을 취할 지 정의한 객체이다.

// payload없음
{ type: 'PARENTS' }

// payload있음
{ type: 'NONE', payload: '' }

type은 필수이다. 행동에 대한 value는 대문자로 명시하고, YOU_GENIUS와 같이 뱀형으로한다.

// without payload
const employee = () => {
  return {
    type: "EMP",
  };
};

//with payload
const setNone = (str) => {
  return {
    type: "NONE",
    payload: str,
  };
};

2-4. Dispatch

Action객체를 전달해주는 함수이다.

// Action 객체를 직접 작성하는 경우
dispatch({ type: "EMP" });
dispatch({ type: "NONE", payload: "" });

// 액션 생성자(Action Creator)를 사용하는 경우
dispatch(employee());
dispatch(none(""));

2-5. Redux Hooks

React-Redux에서 Redux를 사용하는 Hooks 메서드를 사용할 수 있다.

2-5-1. useDispatch()

Action객체를 Reducer로 전달해주는 Dispatch함수를 반환하는 메서드이다.

import { useDispatch } from "react-redux";

const dispatch = useDispatch();
dispatch(employee());
console.log(option); // 직장인

dispatch(none(""));
console.log(option); // ''

2-5-2. useSelector()

component와 state를 연결하여 redux의 state에 접근할 수 있게하는 메서드이다.

// Redux Hooks 메서드는 'redux'가 아니라 'react-redux'
import { useSelector } from "react-redux";
const option = useSelector((state) => state);
console.log(option); // ''

결과문제

2-6. Redux 3원칙

  1. Single source of truth: 동일한 데이터는 항상 같은 곳에서 온다. Store, 단하나의 공간이 있고 연결되어 있다.
  2. State is read-only: 상태는 읽기전용으로 React와 마찬가지로 Redux도 상태를 직접 변경할 수 없다.(Action으로)
  3. Changes are made with pure functions: 변경은 순수함수로만 가능하다. 변조가 없도록 순수함수로 작성되어야한다.

참조

Redux: main
React-Redux: main
robinwieruch: Redux
facebook: flux