Post

MobX

MobX

MobX와 React Context를 모두 사용하여 [store -> context(provider), useHoC -> container] 구조로 사용 중
:: HoC? High-order Component


MobX가 왜 필요한가?

  • MobX는 React에 종속적이지 않은 독립적으로 사용 가능한 라이브러리다. (하지만 대체로 React와 함께 사용한다.)
  • observer가 observable 지정된 state를 감시하다가, state에 변화가 생기면 re-render한다.
    • vanilla React는 setState를 사용해야 하지만, MobX에서는 이를 사용하지 않는다. 알아서 observer가 observable을 관찰하고 re-render 한다.
    • observable은 도메인 객체, observer는 이를 감시하는 액터라고 생각하면 된다.
    • each component individually re-renders when relevant data changes.
  • MobX를 사용할 때, observer가 안붙으면, 해당 react component에서 참조하고 있는 변수(state)에 변화가 생겨도 re-render가 일어나지 않는다. (re-render만 안될 뿐 함수 호출이라던지 나머지는 동작함)

헌데 생각해보면 React만 사용해도 state가 변경되면 그를 참조하고 있는 컴포넌트(view)가 re-render 된다.

단지 observable의 re-render만 필요한 거 였다면 그냥 리액트 써도 될텐데, 왜 MobX가 필요한 것일까?

MobX나 Redux는 “state 관리 라이브러리” 다.

상태 관리 라이브러리란, 말 그대로 state를 쉽게 관리하도록 지원하는 라이브러리다.

  1. 상태 업데이트 로직 분리 가능
  2. 더 손쉬운 상태 관리 (라이브러리 코드 스타일. 개발 방식) - setState 걷어내기
  3. 성능 : state 변경 시 발생하는 re-render 최적화
  4. 미들웨어 등 (Redux의 경우)

글로벌 상태 관리?

  • 이전에는 MobX가 글로벌 상태 관리 하나만으로도 가치가 있었는데, React가 Context API를 지원하기 시작하면서 React 만으로도 글로벌 상태 관리가 가능해졌다.
  • 그래서 단순히 글로벌 상태만 사용하려는 목적이라면 Context API만으로도 커버가 되나, 상태 관리 라이브러리가 글로벌 상태 관리만 제공하는 것은 아니다.

일단 useState(setState)를 안써도 된다는 점이 큰 장점.

setState는 아래 단점이 있다.

  • = 할당 하면 안되고 반드시 setState 사용해야 한다는 것.
  • 비동기로 동작하다 보니 변경이 덮어써질 가능성이 있다는 것.
  • 새로운 값으로 바꾸고 싶을 때 딱 그 필드만 바꾸지 못하고 Object.assign 또는 ... 해야 된다는 것.
  • https://simsimjae.tistory.com/448

setState 안쓰고 state를 업데이트 하는 건 useReducer를 쓰면 보완이 가능하지만, 한계가 있어보인다.

리렌더링 효율성

  • React.Component를 예로 들면, 컴포넌트가 re-render 되어야 하는지를 shouldComponentUpdate 리턴값을 보고 판단함.
  • 이를 구현하지 않는 경우 컴포넌트는 항상 재 렌더링을 하게 됨 (props, state 변경 사항이 없는 경우에도 re-render )
    • 이를 피하기 위해서 useMemo 등등 사용해야 하나…
  • MobX observer를 사용하면 변경이 있는 경우에만 re-render 하므로 효율적임. (물론 다른 방법을 사용할 수도 있다)

기타 MobX를 통한 개선 사례

MobX 어떻게 사용해야 하나

기본적으로 Store를 분리하는 형태

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const MyContext = createContext();

export const MyProvider = ({ children }) => {
    const store = useLocalObservable(createMyStore);
    return <MyContext.Provider value={store}>{children}</MyContext.Provider>
}

export const useMyStore = () => {
    return useContext(MyContext);
};

const createMyStore = () => ({
    ...state와 actions...
})

observer의 주요 역할

The observer HoC automatically subscribes React components to any observables that are used during rendering.
As a result, components will automatically re-render when relevant observables change. It also makes sure that components don’t re-render when there are no relevant changes. So, observables that are accessible by the component, but not actually read, won’t ever cause a re-render.
MobX reacts to any existing observable property that is read during the execution of a tracked function(observer).
https://ko.mobx.js.org/understanding-reactivity.html

observer와 observable은 항상 사용하면 되는건가?

You might be wondering, when do I apply observer? The rule of thumb is: apply observer to all components that read observable data.
Usually all your components should be wrapped by observer.
Don’t worry, this is not inefficient. On the contrary, more observer components make rendering more efficient as updates become more fine-grained.

observer 내에 observable이 어떤 출처로 왔는지, 어떻게 전달되었는지는 상관없다. 하지만 React Context를 사용해 전달하는 것을 추천

For observer to work, it doesn’t matter how the observables arrive in the component, only that they are read.

observer가 아닌 컴포넌트에 observable을 넘기지 마라

너무 빨리 필드를 풀어서 전달할 필요는 없다 (역참조는 최대한 늦게 해라)

1
2
Slower : <DisplayName name={person.name} />
Faster : <DisplayName person={person} />
  • (중요) MobX는 observable 값이 변경 되었을 때, 해당 값을 참조(referencing)하고 있는 컴포넌트들을 모두 re-render 하기 때문임.
    • 매우 중요. 참조 하는 것 만으로도 re-render 대상이 되기 때문에 불필요한 re-render가 발생 할 수 있음.
    • computed를 활용하자. 스마트에디터 개선 사례 참조
    • computed는 값을 캐싱하고 있다가 내부 값에 변경이 발생하면 body를 실행하며 캐시를 갱신함
  • person.name은 여기서 쓰이는 코드가 아닌데 풀어서 전달했다고 가정하자.
    • 그냥 person 넘겨도 상관 없는 상황.즉 person.name이 변경되었을 때 현위치 컴포넌트는 re-render 될 필요가 없는 상황임.
  • 이 때 만약 person.name이 변경된다면 이를 참조하고 있는 현위치 컴포넌트까지 re-render 대상이 된다.
  • There is nothing wrong with that, and if rendering of the owning component is fast enough (usually it is!), then this approach works well.

리스트 렌더링에는 전용 컴포넌트를 사용하는 것이 좋다

  • 물론 리스트 컴포넌트 중에서, 변경이 있는 컴포넌트만 re-render되는 것은 맞다.
  • 하지만 re-rendering 되지 않도록 하면 끝인가? 아니다.
  • 조정(reconcile) 프로세스 자체가 비용이 많이 들기 때문. (상세한 내용과 Good practice )

사례

This post is licensed under CC BY 4.0 by the author.