redux 사용해보기

otter·2022년 6월 6일
0

리덕스를 사용하는 이유

redux 는 상태관리를 도와주는 도구로, 다수의 컴포넌트 또는 App 전체의 상태를 다룰 수 있도록 도와준다. 그런데 이런 점에서 보면 context 로도 충분하지 않나라는 생각이 들 수도 있다. 그렇다면 context가 있는데 redux를 사용하는 이유는 무엇일까?

  • Context를 사용하면 상태 관리가 복잡해질 수 있다.
    • 대형 어플리케이션을 구현한다면 굉장히 많은 Context 를 구분해서 사용해야 하고 이는 Provider 도 중첩해야 한다는 것을 말한다.
    • 만약, Context 하나만을 사용한다고 한다면 Context 안에 너무나 많은 내용이 담길수도 있다.
  • 또한, Context는 성능상의 문제점이 있다.
    • Context 는 빈도가 낮은 변경이 있을땐 좋지만 상태가 빈번하게 변경되는 상태에는 성능상 좋지 않다.

실제로 얼마전 했던 프로젝트에서 겪었던 문제들이었다. 각기 다른 인원수, 캘린더, 가격이라는 상태를 전역적으로 공유해야했고 이를 위해 context를 사용하고자 했다. 그런데 이를 효율적으로 사용하기 위해 - 컨텍스트 사용으로 인한 리렌더링 문제를 해결하기 위해 - context를 세개로 나누어야 했다. 근데 세개를 나누는 것에서 그치지 않았다. 컨텍스트의 리렌더링 문제를 약간이나마 해소하기 위해, 내부적으로는 단순히 상태만을 전달하는 context와 이를 dispatch하기 위한 컨텍스트로도 나누었기 때문이다. 결과적으로 이를 위해서 6가지 context를 만들어야 했다.

이 프로젝트는 아주 간단하고 작은 프로젝트였는데도 6개의 context를 만들어야 했는데 만약 대규모 프로젝트라면 상상할 수 없는 수의 context를 만들어야 했을 것이다. 또한 위처럼 만들었음에도 컨텍스트 사용으로 인한 리렌더링 문제를 완벽히 해소할 수 없었다. 그리고 provider가 세개만 되었을 뿐인데 약간 정신없었다.

리덕스는 이러한 context의 문제를 어느정도 해소시켜 준다.

리덕스의 작동방식


리덕스는 중앙에 존재하는 단 하나의 store를 사용한다.

  • 컴포넌트는 store 를 구독하고 있다.
    • 상태를 바꿔야 한다면 컴포넌트는 저장된 데이터를 직접 조작하지 않는다.
    • 저장소의 상태를 바꾸는 것은 reducer FN 이 담당한다.
  • 상태를 바꿔야 한다면 (예를들어, 컴포넌트의 버튼을 눌렀다면)
    • 컴포넌트는 단순한 js Object인 Action을 dispatch한다.
    • Action은 reducer FN 을 실행한다.
    • reducer FNstore 의 상태를 변경한다.
  • 이렇게 변경된 상태를 component 가 받아서 다시 렌더링한다.

리덕스의 기본구조

// counter.js
// 1. 초기 상태 정의하기
const initialState = { counter: 0 };

// 2. 액션 타입 정의하기
// 액션타입은 관례상 대문자로 작성한다.
// 이렇게 액션타입을 미리 정의해두는 방법을 통해서 ide의 자동완성을 이용하고
// 오타가 나는 등의 오류를 방지할 수 있다.
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

// 3. 액션 생성 함수 정의하기

export const increase = () => {
  return {
    type: INCREASE,
  };
};

export const decrease = () => {
  return {
    type: DECREASE,
  };
};

const counterReducer = (state = initialState, action) => {
  if (action.type === INCREASE) {
    return {
      counter: state.counter + 1,
    };
  }

  if (action.type === DECREASE) {
    return {
      counter: state.counter - 1,
    };
  }

  return state;
};

export default counterReducer;
// 리덕스의 상태는 읽기 전용이므로 상태 그 자체를 변경하면 안된다.
// 리듀서에서는 꼭 상태의 불변성을 지켜주어야 한다.

// 이렇게 3가지를 다 만들면 루트리듀서로 export한다.
// store.js
import { createStore } from 'redux';
import counterReducer from './counter';

// 지금은 리듀서가 하나인 상태여서 루트리듀서를 만들 필요가 없다.
// 하지만 리듀서가 두개 이상이라면 combineReducers를 사용해서 리듀서들을 합칠 수 있다.
// const rootReducer = combineReducers({
//     counterReducer,
//     ...다른 리듀서
// })

const store = createStore(counterReducer);


// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import store from './store/store';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
);
// 이런 방식으로 만들어진 store를 적용한다.

컴포넌트에서 리덕스 store 사용하기

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increase, decrease } from './store/counter';
// 이 increase, decrease는 counter에서 만들었던 액션생성함수이다.
// export const increase = () => {
//   return {
//     type: INCREASE,
//   };
// };
// 이런식으로 만들어져 있다.

function App() {
  const number = useSelector((state) => state.counter);
  // useSelector을 통해서 store에 저장해둔 상태를 불러올 수 있다.

  const dispatch = useDispatch();
  const onIncrease = () => dispatch(increase());
  const onDecrease = () => dispatch(decrease());
  // 이렇게 만들어둔 액션생성함수를 사용한다.

  return (
    <div className="App">
      <button onClick={onIncrease}>plus</button>
      <button onClick={onDecrease}>minus</button>
      <div>changed by redux</div>
      <div>{number}</div>
    </div>
  );
}

export default App;

리덕스를 살짝 공부해보고 난 생각

사실 리덕스를 공부는 진짜 조금 했었고 리덕스 toolkit을 공부했다. 그래서 구현을 할때에도 거의 toolkit을 이용해서 구현했었다. 그래서 액션생성자함수라던지 toolkit에서 자동으로 만들어준다는 액션객체라던지를 이해할 수 없었는데 조금 이해할 수 있었다.
그리고 리덕스의 코드가 길다라는 부분을 알 수 있었다.
또한, 리덕스의 리듀서들은 모두 순수함수들이여야 하므로 값이 바뀌는게 당연한 비동기 fetch로직등에 미들웨어를 사용해야 한다던지 또는 다른 라이브러리를 사용해야 한다던지를 이해할 수 있었다.

reference

https://react.vlpt.us/redux/01-keywords.html

profile
http://otter-log.world 로 이사했어요!

0개의 댓글