카테고리 없음

React vol.9 (Redux)

곽빵 2022. 8. 11. 16:12

개요

React JS 를 공부하면서 기본을 정리해두고자 작성

 

Redux?

Redux에 대해 설명하기 전에 우선 알아야 하는게 있다.

SPA로 웹앱을 제작할 때 절대 빼 놓을수 없는 요소가 "상태"이다. 이러한 상태에도 스코프에 따라 분류하면

  • Local State: useState, useReducer 같은 친구들을 이용해 현 컴포넌트 안에서만 쓰여지는 상태
  • Crros-Component State: useState, useReducer로 상태를 만들지만, 그걸 props chains prop drilling으로 여러 컴포넌트에서 쓰여지는 상태
  • App-Wide State: context-api같은 친구로 앱의 최상단에서 props chains prop drilling으로 전역으로 쓰여지는 상태

여기서 App-Wide State의 상태관리 문제해결을 위해 쓸 수 있는 라이브러리가 Redux이다.

(Redux는 React의 상태관리를 위한 라이브러리가 아니다 즉, Redux는 상태관리 문제해결을 원하는 어떤 곳에서든 사용될 수 있다.)

 

Redux vs Context API

근데 위에서 적어 놓은 것 처럼 이미 App-Wide State의 상태관리를 react의 내장 Context API 로 관리해 줄 수 있는데 왜 Redux가 왜 쓰여지고 있을까? 물론 이유가 있다고 생각해 찾아본 결과를 말하자면,

  • 앱의 규모가 커지면 커질수록 전역상태로 관리해야 하는 항목들도 비대해진다. 그런 상황에서 Context로 전역상태를 관리하는 것은 점점 더 복잡해진다.
  • Performance 관련으로도 이슈가 있다. Context API는 자주 바뀌는 상태에 대해서 최적화가 되어 있지 않다.

앱의 규모가 커졌을 때 Context Provider의 형태
리액트 개발자가 올린 글로써 Context는 Redux를 완벽히 대체할 수 없다고 한다.

 

이러한 이유가 있기 때문에 React를 배운다면 Redux로 당연히 배워야 한다.

 

Redux의 핵심 구성요소

  • State: store의 상태(데이터)
  • Mutates: store의 상태를 바꾸는 동작
  • Reducer: Mutates를 실제로 실행하는 친구
  • Action: Reducer에게 특정한 상태를 바꿔달라고 의뢰하는 동작
  • Dispatch: Action을 실제로 실행하는 친구
  • Subsciption: State의 상태가 바뀌면 자동으로 평가되는 친구

코드로 간단하게 풀어보자면

npm i redux
const redux = require("redux");

// Reducer
const counterReducer = (state = { counter: 0 }, action) => {
  // counter를 1 올리는 Mutate를 할게
  if (action.type === "INCREMENT") {
    return {
      counter: state.counter + 1,
    };
  }
  return state;
};

const store = redux.createStore(counterReducer);

// Subscription 등록
const counterSubscriber = () => {
  const lastState = store.getState();
  console.log("Subscribe Trigger!", lastState);
};
store.subscribe(counterSubscriber);

console.log(store.getState());

// type: INCREMENT라는 Action을 날리는 dispatch
store.dispatch({ type: "INCREMENT" });

 

실행 결과

 

React에서 Redux를 사용하는 경우에는 쉽게 사용할 수 있게 react-redux라는 라이브러리가 존재한다.

npm i redux react-redux

src/store/index.js

import { createStore } from "redux";

const counterReducer = (state = { counter: 0 }, action) => {
  if (action.type === "INCREMENT") {
    return {
      counter: state.counter + 1,
    };
  }
  if (action.type === "DECREMENT") {
    return {
      counter: state.counter - 1,
    };
  }
  return state;
};

const store = createStore(counterReducer);

export default store;

src/index.js

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

import "./index.css";
import App from "./App";

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

src/components/Counter.js

import { useSelector, useDispatch } from "react-redux";

import classes from "./Counter.module.css";

const Counter = () => {
  const dispatch = useDispatch();
  const counter = useSelector((state) => state.counter); // 자동으로 subscription 등록

  const toggleCounterHandler = () => {};

  const incrementHandler = () => {
    dispatch({ type: "INCREMENT" });
  };
  const decrementHandler = () => {
    dispatch({ type: "DECREMENT" });
  };

  return (
    <main className={classes.counter}>
      <h1>Redux Counter</h1>
      <div className={classes.value}>{counter}</div>
      <div>
        <button onClick={incrementHandler}>Increment</button>
        <button onClick={decrementHandler}>Decrement</button>
      </div>
      <button onClick={toggleCounterHandler}>Toggle Counter</button>
    </main>
  );
};

export default Counter;

useDispatch: dispatch를 날리기 위해 쓰는친구

useSelector: store에서 데이터를 취득하기 위한 친구 (자동 subscription 등록 내부적으로 store의 상태가 변하면 useSelector에 넘긴 콜백함수가 자동으로 실행되서 리렌더링 되는 구조?를 갖고 있다고 추측된다.)