Redux VS MobX

shorry·2022년 6월 21일
0

State Management Library

목록 보기
2/2

  • 이번 포스트에서는 React의 대표적인 상태관리 라이브러리인 Redux 와 MobX에 대해서만 다룹니다. React 자체적으로 처리 할 수 있는 Context가 존재하지만 대규모 프로젝트에서는 여전히 Redux나 Mobx가 유리합니다.
  • tistory(jungpaeng) - ContextAPI 렌더링 이슈

📌Redux


  • MVC 패턴의 문제점을 극복하고자 페이스북에서 고안한 Flux 아키텍처를 기반으로 구현되었다.
  • Redux에 대한 자세한 설명은 velog(@shorrysorry) - Redux 참고

✔️Redux 의 특징


  • 상태를 전역적으로 관리하기 때문에 어느 컴포넌트에 상태를 둬야할지 고민할 필요가 없게 한다.
  • 데이터 흐름을 단방향으로 흐르게 한다.
  • 상태 관리에서 불변성 유지가 매우 중요하다. 상태를 읽기전용으로 취급한다. Immutable.js 와 같은 라이브러리가 쓰이기도 한다.
  • 불변성 유지를 통해 디버깅에 도움을 줄 수 있다. Redux 를 더욱 쉽게 테스트 + 유지 보수 관리 및 확장을 할 수 있다.
  • flux 아키텍처를 따른다. (dispatch 관리를 위해 redux-thunk나 redux-saga와 같은 미들웨어가 필수이다.)
  • 비동기 처리를 위해서 Redux는 별도의 라이브러리를 추가적으로 사용해야한다.(redux-thunk, redux-saga).
  • 개념에 대한 이해가 선행되어야 하고, 여러 라이브러리를 함께 사용하는 경우가 있기 때문에 러닝커브가 높은 편이다.
  • 액션을 하나 추가하는데, 작성 필요한 부분이 많고, 컴포넌트와 스토어를 연결하는 필수적인 부분들이 있어 코드량이 많아질 수 있다.
  • 상태 관리 라이브러리에서 Redux 는 가장 큰 커뮤니티 지원을 제공한다.

📌MobX


  • Redux보다는 덜 알려졌지만 사용하기 쉽고 강력한 기능으로 꾸준한 성장을 하고 있는 상태 관리 라이브러리입니다.
  • React에서 MobX를 쉽게 사용하게 해주는 써드파티 라이브러리로, mobx-react와 mobx-react-lite가 있는데, mobx-react는 클래스형 컴포넌트와 hooks를 모두 지원하고, mobx-react-lite는 훅스만 지원한다.
  • MobX를 사용하려는 프로젝트에서 이미 hooks를 사용중이라면, 조금 더 가벼운 mobx-react-lite 사용을 권장한다.
  • 또한, MobX 6에서 decorators(ex. @action, @observable 등)들이 deprecated 되었다.

✔️MobX 의 특징


  • React에 종속적인 라이브러리가 아니고, Redux와 다르게 store에 제한이 없다.
  • observable을 기본적으로 사용하고, 절대적으로 필요한 경우에만 state를 변경한다.
  • 객체 지향적이다. 그렇기 때문에 서버 개발자들에게 친숙한 아키텍쳐를 제공할 수 있다. 특히 JAVA Spring과 유사하다.
  • Redux에서 컴포넌트와 state를 연결하기 위해 사용하는 mapStateToProps, mapDispatchToProps 등의 보일러플레이트 코드가 사라지고 데코레이터를 통해 깔끔한 코드 작성이 가능하다.
  • MobX는 비동기 처리를 할 때, async action을 지원해서 async/await 문법을 사용해 깔끔하게 처리가 가능하다.
  • State의 불변성 유지를 위해 노력하지 않아도 된다. Redux에서는 state의 불변성 유지를 위해 여러 라이브러리를 사용하기도 하는데, Mobx에서는 그러지 않아도 된다.
  • Mobx는 배우기 쉽고 러닝 커브가 낮다.
  • 클래스형 컴포넌트 기준으로 맞춰져 있다.
  • 함수형으로도 사용이 가능하지만, 검색을 해도 버전별 데코레이터의 사용여부나 프로젝트 별로 조금씩의 차이가 있어 제대로된 자료를 찾아보기가 힘들다.

✔️MobX의 구조


Actions


Observable State에 저장되어 있는 데이터들을 변화시키는 액션 함수

Observable State


관찰되고 있는 데이터 값들이 저장되어 있는 장소

Compute Values


Observable State에 저장되어 있는 데이터가 변화되는 것을 알아채면 렌더링과 같은 Side Effects trigger를 전달

Side Effects


렌더링과 같은 Side Effects가 실행되고 실행된 Side Effects들은 다시 액션 함수가 실행되도록 이벤트 전달

✔️MobX 의 핵심 개념


  • MobX는 observable을 사용하면 properties, entire objects, arrays, Maps, Sets 등을 모두 자동으로 observable(관찰 가능한)하게 만들 수 있다.
  • 여기에 가장 중요한 어노테이션(annotation)으로는 아래 세 가지가 있다.
  1. observable : 추적 가능한 state 정의
  2. action : state를 변경하는 메소드
  3. computed : state와 캐시로부터 새로운 결과를 반환

observable


  • observable은 makeObservable, makeAutoObservable 그리고 observable 이 세 가지가 있으며, 모두 추적 가능한 상태의 state로 만들어준다.

action


  • action은 state를 변경하는 것을 뜻한다.
  • makeObservable을 사용하면 action을 따로 작성해줘야 하지만, makeAutoObservable은 이를 대신해준다.

computed


computed values(계산된 값)는 다른 observable들에서 어떠한 정보를 도출하는데 사용할 수 있다.

✔️MobX 사용 예시(1)


src/store/countClass.ts - makeObservable ver.


import { action, makeObservable, observable } from 'mobx'

class Count {
  number: number = 0

  constructor() {
    makeObservable(this, {
      number: observable,
      increase: action,
      decrease: action,
    })
  }

  increase = () => {
    this.number++
  }
  decrease = () => {
    this.number--
  }
}

const countStore = new Count()
export default countStore

src/store/countClassAuto.ts - makeAutoObservable ver.


import { makeAutoObservable } from 'mobx'

class Count {
  number: number = 0

  constructor() {
    makeAutoObservable(this)
  }

  increase = () => {
    this.number++
  }
  decrease = () => {
    this.number--
  }
}

const countStore = new Count()
export default countStore

src/store/countObejct.ts - object ver.


import { observable } from 'mobx'

const countObject = observable({
  // 헷갈릴 수 있으니 num으로 작명
  num: 0,
  increase() {
    this.num++
  },
  decrease() {
    this.num--
  },
})

export default countObject

src/store/index.ts


import countClass from './countClass'
import countObject from './countObject'

const store = { countClass, countObject }
export default store

src/App.tsx


import React from 'react'
import { observer } from 'mobx-react'
import store from './store'

// 컴포넌트를 observer로 감싸주어 state가 실시간으로 변경되는 것을 감지한다
const App: React.FC = observer(() => {
  const { countClass, countObject } = store

  return (
    <div style={{ padding: '50px' }}>
      <div style={{ marginBottom: '50px' }}>
        <h1>Count (Class)</h1>
        <div>number: {countClass.number}</div>
        <button onClick={() => countClass.increase()}>plus</button>
        <button onClick={() => countClass.decrease()}>minus</button>
      </div>

      <div style={{ marginBottom: '50px' }}>
        <h1>Count (Object)</h1>
        <div>num: {countObject.num}</div>
        <button onClick={() => countObject.increase()}>increment</button>
      </div>
    </div>
  )
})

export default App

✔️MobX 사용 예시(2)


CategoryStore


  • Category 컴포넌트에 사용할 Store 생성
import React from 'react';
import { makeObservable, observable, action } from 'mobx';
class CategoryStore {
  // 관찰 대상 초기값
  categories = [];

  constructor() {
    makeObservable(this, {
      categories: observable,
      addCategory: action,
      removeCategory: action,
    });
  }

// action 메소드
  addCategory = (item) => {
    this.categories = [...this.categories, item];
  };
  removeCategory = (item) => {
    this.categories = this.categories.filter((each) => {
      each.id !== item.id;
    });
  };
}

// React 내장기능인 Context를 사용하여 custom hook 생성 
const categoryStore = new CategoryStore();
export const CategoryStoreContext = React.createContext(categoryStore);
export const useCategoryStore = () => React.useContext(CategoryStoreContext);

Category Component


// 컴포넌트에서 사용할 observer와 custom hook import
import { observer } from 'mobx-react';
import { useCategoryStore } from '../store/CategoryStore';

// 중략

// 구조분해 할당
const { categories, addCategory, removeCategory } = useCategoryStore();

// 중략

// 사용하고자 하는 곳에 사용
<CheckBox
        id={index}
        title={item.name}
        checkedColor="#EF904F"
        uncheckedIcon="check-circle"
        checkedIcon="check-circle"
        onPress={() => {
          !categories.includes() ? addCategory(item) : removeCategory(item);
          console.log(categories);
        }}
        checked={categories.some((each) => each.id === item.id) ? true : false}
      />

// 중략

// 값의 변화를 관찰할 수 있게 observer 고차컴포넌트를 이용해 감싸준다.
export default observer(CategorySearch);

📌Reference


profile
I'm SHORRY about that

0개의 댓글