Redux

ding·2024년 4월 17일

Redux를 써야 할 상황

  • 계속해서 바뀌는 상당한 양의 데이터가 있다
  • 상태를 위한 단 하나의 근원이 필요하다
  • 최상위 컴포넌트가 모든 상태를 가지고 있는 것은 더 이상 적절하지 않다

자식 컴포넌트들 간의 데이터를 주고 받을 때는 상태를 관리하는 부모 컴포넌트를 통해서 주고 받는다.
자식이 많아질 경우 상태 관리가 매우 복잡해진다. 이럴 때 사용하면 좋은 것이 redux이다.


리덕스를 사용하면 상태값을, 컴포넌트에 종속시키지 않고, 상태 관리를 컴포넌트의 바깥에서 관리 할 수 있게 된다.

Store

상태가 관리되는 오직 하나의 공간이다.
컴포넌트와는 별개로 스토어라는 공간이 있어서 그 스토어 안에 앱에서 필요한 상태를 담는다.
컴포넌트에서 상태 정보가 필요할 때 스토어에 접근한다.

Action

앱에서 스토어에 운반할 데이터
Action(액션)은 자바스크립트 객체 형식으로 되어있다.

Reducer

Action(액션)을 Store(스토어)에 바로 전달하는 것이 아니다.
Action(액션)을 Reducer(리듀서)에 전달해야한다.
Reducer(리듀서)가 주문을 보고 Store(스토어)의 상태를 업데이트하는 것이다.
Action(액션)을 Reducer(리듀서)에 전달하기 위해서는 dispatch() 메소드를 사용해야한다.
Action(액션) 객체가 dispatch() 메소드에 전달된다.

dispatch(액션)를 통해 Reducer를 호출한다.

Reducer는 새로운 Store 를 생성한다.

Redux 흐름

1. 스토어 설정


리덕스를 프로젝트에 적용하게 되면 스토어가 생긴다.
스토어 안에는 프로젝트의 상태에 관한 데이터들이 담겨있다.

2. 컴포넌트의 스토어 구독


G 컴포넌트는 스토어에 구독을 한다. 구독을 하는 과정에서, 특정 함수가 스토어한테 전달이 된다. 그리고 나중에 스토어의 상태값에 변동이 생긴다면 전달 받았던 함수를 호출한다.

3. 스토어에 상태 변경하라고 알려주기


B 컴포넌트에서 어떤 이벤트가 생겨서, 상태를 변화 할 일이 생겼다. 이 때 dispatch 라는 함수를 통하여 액션을 스토어한테 던져준다. 액션은 상태에 변화를 일으킬 때 참조 할 수 있는 객체다. 액션 객체는 필수적으로 type 라는 값을 가지고 있어야 한다.

예를들어 { type: 'INCREMENT' } 이런 객체를 전달 받게 된다면, 리덕스 스토어는 아~ 상태에 값을 더해야 하는구나~ 하고 액션을 참조하게 된다.
상태값에 2를 더해야 한다면, 이러한 액션 객체를 만들게 된다: { type: 'INCREMENT', diff: 2 }
type 를 제외한 값은 선택적(optional) 인 값이다.

4. 리듀서로 상태 변화시키기


액션 객체를 받으면 전달받은 액션의 타입에 따라 어떻게 상태를 업데이트 해야 할지 정의해야하는데, 이러한 업데이트 로직을 정의하는 함수를 리듀서다. 예를들어 type 이 INCREMENT 라는 액션이 들어오면 숫자를 더해주고, DECREMENT 라는 액션이 들어오면 숫자를 감소시키는 작업을 여기서 한다.

리듀서 함수는 두가지의 파라미터를 받는다.

  • state: 현재 상태
  • action: 액션 객체

이 두 가지 파라미터를 참조하여 새로운 상태 객체를 만들어서 이를 반환한다.

5. 상태가 변화가 생기면, 구독하고 있던 컴포넌트에게 알림


상태에 변화가 생기면 이전에 컴포넌트가 스토어한테 구독 할 때 전달해줬었던 함수 listener 가 호출된다. 이를 통하여 컴포넌트는 새로운 상태를 받게 되고, 이에 따라 컴포넌트는 리렌더링을 하게 된다.

Redux 사용

1. redux install

npm install redux
npm install react-redux

2. RootReducer 정의

// reducers/index.js

/** root reducer */
import { combineReducers } from "redux";
import counter from "./counter";

// 여러 reducer를 사용하는 경우 reducer를 하나로 묶어주는 메소드입니다.
// store에 저장되는 리듀서는 오직 1개입니다.
const rootReducer = combineReducers({
  counter
});

export default rootReducer;

3. 세부 reducer 정의

// reducers/counter.js

// reducer가 많아지면 action상수가 중복될 수 있으니
// 액션이름 앞에 파일 이름을 넣습니다.
export const INCRESE = "COUNT/INCRESE";

export const increseCount = count => ({ type: INCRESE, count });

const initalState = {
  count: 0
};

const counter = (state = initalState, action) => {
  switch (action.type) {
    case INCRESE:
      return {
        ...state,
        count: action.count
      };

    // default를 쓰지 않으면 맨처음 state에 count값이 undefined가 나옵니다 꼭! default문을 넣으세요
    default:
      return state;
  }
};

4. app에 store 넣고 만든 reducer 반영

// index.js
import React from "react";
import ReactDOM from "react-dom";
import { createStore, applyMiddleware, compose } from "redux";
import { Provider } from "react-redux";
import logger from "redux-logger";
import { composeWithDevTools } from "redux-devtools-extension";

import App from "./App";
import rootReducer from "./reducers";

// 배포 레벨에서는 리덕스 발동시 찍히는 logger를 사용하지 않습니다.
const enhancer =
  process.env.NODE_ENV === "production"
    ? compose(applyMiddleware())
    : composeWithDevTools(applyMiddleware(logger));

// 위에서 만든 reducer를 스토어 만들때 넣어줍니다
const store = createStore(rootReducer, enhancer);

ReactDOM.render(
  // 만든 store를 앱 상위에 넣어줍니다.
  <Provider store={store}>
    <App />
  </Provider>
  document.getElementById('root'),
);

5. 컴포넌트에서 Redux 사용하기

import { useSelector, useDispatch } from "react-redux";
import { increseCount } from "reducers/count";

// dispatch를 사용하기 위한 준비
const dispatch = useDispatch();

// store에 접근하여 state 가져오기
const { count } = useSelector(state => state.counter);

const increse = () => {
  // store에 있는 state 바꾸는 함수 실행
  dispatch(increseCount());
};

const Counter = () => {
  return (
    <div>
      {count}
      <button onClick={increse}>증가</button>
    </div>
  );
};

export default Counter;

0개의 댓글