본문 바로가기
react

MobX는 왜 선택을 받았을까?

by 아촌 2025. 5. 15.

 

최근 노빌더툴에 대한 관심이 많아 관련 기술들을 찾아보던 중, 토스에서 피그마나 Framer를 대체할 자체 디자인 편집기를 만들고 있다는 사실을 알게 되었습니다. 그리고 이 프로젝트의 상태 관리 라이브러리로 MobX를 선택했다는 것을 접했습니다.

 

https://toss.tech/article/firesidechat_frontend_11

 

토스의 디자인 편집기 ‘데우스’, 이렇게 만들었어요! | EP.11

토스의 디자인 편집기 데우스(Deus)! 이번 모닥불에서는 토스의 자체 디자인 편집기 데우스(Deus) 프로젝트를 소개합니다! 디자인과 개발 사이의 경계를 넘나드는 새로운 가능성을 만나보세요! 데

toss.tech

 

 

 

저는 클라이언트 상태 관리에서 MobX를 사용해 본 경험이 없었습니다. 때문에 이번 기회에 MobX에 대해 자세히 알아보게 되었습니다.

 

Mobx 소개

MobX 공식 문서에서는 MobX의 핵심 철학을 다음과 같이 이야기합니다.

 

애플리케이션 상태로부터 도출될 수 있는 모든 것은 자동으로 도출되어야 한다.

MobX는 signal 기반의 검증된 라이브러리로, 함수형 반응형 프로그래밍을 투명하게 적용하여 상태 관리를 간단하고 확장 가능하게 만듭니다. 

 

여기서 흥미롭게 다가온 부분은 '자동으로'와 '신호 기반'이라는 특징입니다. 개발자가 상태 변화를 직접 관리하고 전파하기보다는, MobX가 상태 변화를 감지하고 필요한 부분을 자동으로 업데이트해준다는 점이 매력적으로 느껴졌습니다.

 

React에서 MobX를 사용하기 위한 구성 패키지

리액트에서 Mobx에는 여러 패키지가 있는데

 

1. mobx: MobX의 핵심 라이브러리입니다. 관찰 가능한 상태(@observable), 파생 값(@computed), 상태 변경 액션(@action), 반응(Reaction, autorun 등)과 같은 MobX의 기본적인 기능들을 사용하기 위해 필요합니다.

 

2. mobx-react: React 클래스 컴포넌트함수형 컴포넌트에서 MobX 상태 변화에 반응하도록 observer HOC 또는 데코레이터를 사용할 때 필요했습니다. (과거에는 주로 클래스 컴포넌트와 함께 사용되었습니다.)

 

3. mobx-react-lite: 함수형 컴포넌트에서 MobX 상태 변화에 반응하도록 observer HOC 또는 훅(useObserver)을 사용할 때 사용됩니다. mobx-react보다 가볍고 함수형 컴포넌트 및 React Hooks 환경에 최적화되어 있습니다.

 

4. mobx-react-observer: Babel 또는 SWC와 같은 트랜스파일러 플러그인입니다. 이 플러그인을 사용하면 코드를 빌드할 때 특정 조건을 만족하는 컴포넌트에 observer를 자동으로 래핑해주는 기능을 제공합니다.

 

 

import { observer } from "mobx-react-lite";
import { observable } from "mobx";

const counter = observable({
  count: 0,
  increase() {
    counter.count++;
  },
});

const Counter = observer(function Counter() {
  return (
    <button
      onClick={() => {
        counter.increase();
      }}
    >
      Count {counter.count}
    </button>
  );
});

 

 

mobx-react-observer 설치시에는 따로 observer 래핑을 안해도 자동적으로 변화를 감지합니다.

import { observable } from "mobx";

const counter = observable({
  count: 0,
  increase() {
    counter.count++;
  },
});

function Counter() {
  return (
    <button
      onClick={() => {
        counter.increase();
      }}
    >
      Count {counter.count}
    </button>
  );
}

 

 

 

mobx-react와 mobx-react-lite의 차이?

// packges/mobx-react/src/observer.tsx

import * as React from "react"
import { observer as observerLite } from "mobx-react-lite"

import { makeClassComponentObserver } from "./observerClass"
import { IReactComponent } from "./types/IReactComponent"

/**
 * Observer function / decorator
 */
export function observer<T extends IReactComponent>(component: T, context: ClassDecoratorContext): void
export function observer<T extends IReactComponent>(component: T): T
export function observer<T extends IReactComponent>(component: T, context?: ClassDecoratorContext): T {
    if (context && context.kind !== "class") {
        throw new Error("The @observer decorator can be used on classes only")
    }
    if (component["isMobxInjector"] === true) {
        console.warn(
            "Mobx observer: You are trying to use `observer` on a component that already has `inject`. Please apply `observer` before applying `inject`"
        )
    }

    if (
        Object.prototype.isPrototypeOf.call(React.Component, component) ||
        Object.prototype.isPrototypeOf.call(React.PureComponent, component)
    ) {
        // Class component
        return makeClassComponentObserver(component as React.ComponentClass<any, any>) as T
    } else {
        // Function component
        return observerLite(component as React.FunctionComponent<any>) as T
    }
}

 

mobx-react 내부 코드를 보면 함수형 컴포넌트를 사용중이라면 내부적으로도 mobx-react-lite를 사용하기 때문에

함수형 컴포넌트 사용하고 있다면 mobx-react-lite를 사용하면 된다.

 

 

Mobx에 observer의 동작 원리

mobx-react-lite 내부의 observer는 React 컴포넌트를 Mobx의 반응형 시스템과 연결해주는 High-Order Component(HOC)입니다.

그 내부에 가장 핵심 기능은 원본 컴포넌트의 렌더링 함수를 useObserver로 감싸서 Mobx 상태 변화 추적 및 자동 리렌더링 기능을 추가하는 부분입니다.

 

useObserver는 React 컴포넌트 안에서 MobX의 반응형 시스템과 연결해주는 React Hook이다. observer HOC는 바로 이 useObserver 훅을 사용하여 우리가 만든 일반적인 React 컴포넌트 함수를 '관찰 가능한(Observed)' 컴포넌트로 만들어준다.

 

useObserver 내부에서 동작 원리

1. Reaction 객체를 생성

function createReaction(adm: ObserverAdministration) {
    adm.reaction = new Reaction(`observer${adm.name}`, () => {
        adm.stateVersion = Symbol() 	// 상태 버전 업데이트
        adm.onStoreChange?.()		// React에게 상태 변경 알림
    })
}

 

 useObserver 훅은 내부적으로 MobX의 Reaction 객체를 하나 만듭니다. 이 Reaction 객체는 MobX의 핵심 기능 중 하나로, 특정 MobX @observable 상태들의 변화를 '구독'하고 감지하는 역할을 합니다.

 Reaction이 구독하고 있는 @observable 상태 중 하나라도 변경이 감지되면, Reaction 생성 시 정의된 콜백 함수(() => { ... })가 실행됩니다. 이 콜백 함수 안에서는 adm.stateVersion = Symbol() 코드를 통해 상태 버전 값을 새로운 고유 값(Symbol)으로 업데이트하고, adm.onStoreChange?.() 함수를 호출하여 React에게 "외부 상태가 변경되었을 가능성이 있으니 확인해보라"고 알리는 신호를 보냅니다.

 

2. React의 useSyncExternalStore을 활용 및 연결

 useSyncExternalStore(
        adm.subscribe,
        adm.getSnapshot,
        adm.getSnapshot
    )

MobX Reaction이 상태 변화를 감지하고 콜백을 실행하면, adm.onStoreChange()가 호출되고, React의 useSyncExternalStore는 이 알림을 받아 adm.getSnapshot()을 다시 호출합니다. useSyncExternalStore는 이전 스냅샷과 새로 가져온 스냅샷을 비교하여 다르면 해당 컴포넌트의 리렌더링을 스케줄링하게 됩니다. (stateVersion에 매번 새로운 Symbol을 사용하기 때문에 스냅샷은 항상 다르게 보입니다.

 

3. 렌더링 중 MobX 상태 사용 자동 추적 (reaction.track)

adm.reaction!.track(() => {
    try {
        renderResult = render() // 여러분의 컴포넌트 렌더링 함수 실행
    } catch (e) {
        exception = e
    }
})

이 부분이 MobX 반응성의 핵심 중 하나입니다. useObserver는 인자로 받은 여러분의 컴포넌트 렌더링 함수 (render)를 Reaction.track() 메서드 안에서 실행합니다.

핵심 마법: Reaction.track() 안에서 코드가 실행될 때, MobX는 그 코드(즉, 여러분의 렌더링 로직)가 어떤 @observable 상태들을 '읽는지' 자동으로 감지하고 추적합니다. 예를 들어, 렌더링 함수 안에서 store.user.name이라는 @observable 값을 사용하면, MobX는 이 Reaction이 store.user.name에 '의존성(Dependency)'을 갖는다고 내부적으로 기록합니다.

 

 

결론

useObserver 훅은 다음의 두 가지 중요한 메커니즘을 통해 React 컴포넌트가 MobX 상태 변화에 반응하도록 만듭니다.

  • 자동 종속성 추적: 컴포넌트가 렌더링될 때 useObserver는 MobX에게 "지금부터 네가 관리하는 @observable 상태 중 이 컴포넌트가 읽는 것을 모두 기억해 줘" 라고 알려줍니다 (Reaction.track()).
  • 상태 변화 시 React에게 알림: 만약 MobX가 기억해 둔 @observable 상태 중 하나라도 값이 변경되면 (Reaction 발동), useObserver는 React의 useSyncExternalStore 훅을 통해 React에게 "상태가 변했으니 이 컴포넌트를 다시 그려주세요!" 라고 알리고, React는 이 알림을 받고 리렌더링을 진행합니다.

 

덕분에 개발자는 MobX 상태만 잘 변경하면, observer로 감싼 컴포넌트는 자신이 필요한 상태가 변경될 때 자동으로 그리고 효율적으로 리렌더링되는 편리함을 누릴 수 있습니다. React에서 상태 변화를 위한 복잡한 구독/해제 로직을 직접 관리할 필요가 크게 줄어듭니다.

토스의 디자인 편집기 '데우스'처럼 리렌더링 될 부분이 많고 복잡한 프로젝트에서는, 어떤 상태 변화가 어떤 UI 업데이트로 이어지는지 개발자가 일일이 관리하기 어렵습니다. 이런 상황에서 MobX처럼 자동으로 컴포넌트의 최소 부분을 리렌더링 해주는 라이브러리는 엔지니어의 인지 부하를 줄여주고 생산성을 높이는 데 큰 도움이 될 수 있다는 점을 느꼈습니다.