코드노트

Redux의 구조 정리, feat. Flux 패턴 본문

Code note/리액트

Redux의 구조 정리, feat. Flux 패턴

코드노트 2023. 7. 24. 04:45

리덕스는 전역 상태관리 라이브러리이다.

리액트쿼리, SWR, zustand, recoil 등 여러 상태관리 라이브러리가 있지만 기본으로 알고가야하는 라이브러리가 리덕스라고 생각한다.

리덕스의 구조와 Flux패턴에 대해서 정리를 해보려고 한다.

 

리덕스의 구조를 알아보기전

MVC패턴과 Flux패턴을 알아보자


MVC 패턴

MVC 패턴은 Flux 패턴이 등장하기전 사용되던 디자인 패턴이다.

  • Model : 데이터 보관 및 정의
  • Controller : 데이터에 대한 수정, 조회 등의 역할
  • View : 데이터를 화면에 보여주는 역할

- Model에 데이터를 정의해 두고, Controller를 이용해서 Model 데이터를 생성, 조회, 수정, 삭제 등을 하여 변경된 데이터를 View에 출력하여 사용자들에게 보여줄 수 있다.

 

여기서 나오는 문제점이 있었다.

이 패턴은 어플리케이션 규모가 커질수록 데이터 흐름의 복잡도가 늘어난다는것..

이렇게 MVC 패턴은 데이터 변경사항을 신속하게 전파하기가 어렵다. Model이 늘어날수록 전파해야하는 대상도 늘어나고  하나의 Model을 수정했을때 여러 View가 수정되기도 한다. 이렇게 되어버리면 어떤 View가 어떤 Model로부터 수정되었는지 그 흐름을 추적하는것이 복잡해 진다.

 

이러한 복잡한 패턴을 해결하기 위해 나온것이 Flux패턴 이다.


Flux패턴

- 페이스북에서 2014년에 발표한 패턴으로 MVC패턴과는 다르게 단방향 데이터 흐름을 가지고 있다.

  • Action : 데이터에 대해서 정확히 어떤 상태 변화를 할지 지정하여 Dispatcher로 전달
  • Dispatcher : Store 에 Action 을 전달하는 역할 (동기적으로 실행되어 데이터 흐름을 관리)
  • Store : Dispatcher 로부터 action 을 받아서 데이터의 상태를 변경하고 보관 (상태가 변경되면 변경되었다고 알림)
  • View : 데이터를 화면에 보여주는 역할 (데이터를 자식 컴포넌트 등으로 보내주는 컨트롤러의 역할도 함께 함

이렇게 할 경우 Action 객체만 잘 따라가도, 정확히 어떤 데이터 변화가 일어나고 있는지 추적할 수 있다. View에서 사용자와의 상호작용에 따라 새로운 Action 객체가 생성되더라도, 항상 Dispatcher를 통하기 때문에 데이터의 흐름이 한 방향으로 흐르게 된다.

 


리덕스는 이 Flux패턴을 기반으로 만들어졌으며, 특징과 차이점이 있다.

  • Store : 데이터의 보관 역할만 담당함
  • Reducer : 데이터의 갱신 역할을 담당함
  • Dispatcher: 없음 ( Reducer가 대신 Action에 따른 전달을 담당 )

Redux 의 구조

리덕스의 구조는 useReducer를 사용했을때의 구조와 비슷한 구조를 가지고 있다.

 

구현 예 : 카운터를 구현, +1 버튼, -1 버튼으로 증가 및 감소가 가능하도록 구현

 

[Action Type]

export const INC_COUNT = "INC_COUNT";
export const DEC_COUNT = "DEC_COUNT";

- action 정의를 통해서 type을 변수로 미리 만들었다.

- export를 해서 불러오기 및 오타 방지를 위한 의도로 정의

 

[Action 생성 함수] → 원칙적으로 항상 액션 객체를 리턴해야 한다.

export function incCount(diff) {
    return {
        type: INC_COUNT,
        payload: {diff},
    }
}

export function decCount(diff) {
    return {
        type: DEC_COUNT,
        payload: {diff},
    }
}

- 이러한 객체를 redux에서는 action 객체라하며 reducer로 보내는 행위를 dispatch라고 표현한다.

- dispatch를 통해 action이 전달된다.

- 이렇게 dispatch에 action객체를 리턴하는 함수를 전달해서 사용하는 경우도 있다. action 생성 함수이다.

ex) 만약 버튼을 눌렀을 때 2만큼 수가 증가하도록 하는 action 객체를 생성해서 reducer 로 보내고싶다면?

→ dispatch(incCount(2)) 라고 실행

 

[Reducer] → state 는 불변성을 유지해야한다.

function counter(state = initialState, action) {
    switch (action.type) {
      case INC_COUNT:
        return state + 1
      case DEC_COUNT:
        return state - 1;
      default:
        return state;
    }
  }

- 불변성을 유지하기 위해서는 push를 사용하는게 아닌 concat, ... (전개 연산자) 등을 사용해야한다.

- 동일한 파라미터가 들어왔다면 동일한 결과를 출력해야한다. 

- reducer는 state 와 action을 파라미터로 받는다.

 

[Store] → 하나의 앱에는 하나의 스토어가 있도록 구성하는 것이 규칙이다.

import { legacy_createStore as createStore } from "redux";
import counter from "./reducers/counter";

const store = createStore(counter);

export default store;
import { Provider } from "react-redux";
import store from './redux';

function App() {
  return (
    <Provider store={store}>
			<Counter />
    </Provider>
  );
}

export default App;

- 이렇게 해야 Provider를 통해서 컴포넌트에 공유할 수 있다.