MobX introduction [번역]

mobx

Contents

  • Introduction
  • Core concepts

    • Observable state
    • Computed Values
    • Reactions
    • What will MobX react to?
    • Actions
  • MobX: Simple and scalable

    • Using classes and real references
    • Referential integrity is guaranteed
    • Simpler actions are easier to maintain
    • Fine grained observability is efficient
    • Easy interoperability

Introduction

MobX는 많은 프로젝트에서 사용된 검증된 라이브러리로, functional reactive programming을 적용해 앱의 상태 관리를 쉽고 확장이 용이하게 도와줍니다. MobX의 추구하는 방향은 아래와 같아 간단합니다.

Anything that can be derived from the application state, should be derived. Automatically. which includes the UI, data serialization, server communication, etc.

앱의 상태에서 도출할 수 있는 것은 UI, 데이터 직렬화, 서버 커뮤니케이션 등을 포함해 어떠한 것이든 도출되어야 한다는 것이 MobX의 철학이라고 합니다.

React와 MobX를 함께 사용하면 매우 유용합니다. React는 애플리케이션의 State를 렌더 가능한 컴포넌트로 구성 및 앱의 상태를 렌더합니다. MobX는 앱의 상태를 저장하고 수정할 수 있는 메커니즘을 제공합니다.

React와 MobX 두 라이브러리 모두 애플리케이션 개발 시 발생하는 일반적인 문제를 해결하는데 최적화된 솔루션을 제공합니다. React는 virtual DOM을 이용해 비용이 많이 드는 DOM 조작을 최적화함으로써 UI를 렌더합니다. MobX는 application state를 React Component와 최적의 방법으로 동기화 하는 메커니즘을 제공하며, 이를 위해 꼭 필요할 때만 업데이트가 되는 virtual dependency state graph를 이용합니다.

Core Concepts

MobX은 몇몇 핵심 개념이 있으며, 다음의 snippet은 codesandbox를 통해 확인해 볼 수 있습니다.

Observable state

Egghead.io lesson 1: observable & observer

MobX는 observable 기능을 객체, 배열, 클래스 인스턴스와 같은 자료구조에 추가합니다. 단순히 @observable decorator를 클래스에 추가해 이용할 수 있습니다.

import { observable } from 'mobx';

class Todo {
  id = Math.random();
  @observable title = '';
  @observable finished = false;
}

observable의 값으로는 원시값뿐만 아니라, 배열이나 객체가 올 수도 있습니다. 여러분의 개발 환경이 decorator syntax를 지원하지 않는다면, 여기를 참고해서 설정할 수 있습니다. 또는 MobX에서 지원하는 decorate 유틸리티를 이용할 수 있지만, decorator syntax를 사용하는 것이 좀 더 간결합니다.

import { decorate, observable } from 'mobx';

class Todo {
  id = Math.random();
  title = '';
  finished = false;
}

decorate(Todo, {
  title: observable,
  finished: observable
});

Computed Values

Egghead.io lesson 3: computed values

어떠한 관련 데이터가 수정되었을 때, 수정된 값에서 파생된 값을 MobX를 이용해 지정할 수 있습니다. @computed decorator를 이용하거나, extend Observable을 이용할 경우 getter / setter 함수를 이용할 수 있습니다. 물론, decorator syntax 대신 앞서 설명한 decorate 유틸리티를 이용할 수도 있습니다.

class TodoList {
  @observable todos = [];
  @computed get unfinishedTodoCount() {
    return this.todos.filter(todo => !todo.finished).length;
  }
}

MobX는 todo가 추가되거나 finished 속성이 수정되면, 꼭 필요한 경우에 unfinishedTodoCount가 자동적으로 업데이트 되도록 합니다.

Reactions

Egghead.io lesson 9: custom reactions

Reaction은 computed value와 비슷하지만, 새로운 값을 생성하는 대신 console에 log를 기록한다거나 네트워크 요청을 보내거나 React 컴포넌트 트리를 업데이트합니다. 간단히 말하면, reactions는 반응형과 명령형 프로그래밍의 가교 역활을 합니다.

React components

Egghead.io lesson 1: observable & observer

React에서 함수형 컴포넌트를 반응형 컴포넌트로 변경할 수 있습니다. 이를 위해서 mobx-react의 observer 함수 / 데코레이터를 추가해 줄 수 있습니다.

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { observer } from 'mobx-react';

@observer
class TodoListView extends Component {
  render() {
    return (
      <div>
        <ul>
          {this.props.todoList.todos.map(todo => (
            <TodoView todo={todo} key={todo.id} />
          ))}
        </ul>
        Tasks left: {this.props.todoList.unfinishedTodoCount}
      </div>
    );
  }
}

class TodoView = observer(({ todo }) => (
  <li>
    <input
      type="checkbox"
      checked={todo.finished}
      onClick={() => (todo.finished => !todo.finished)}
    />
    {todo.title}
  </li>
));

const store = new TodoList();

ReactDOM.render(
  <TodoListView todoList={store} />,
  document.getElementById('mount');
);

observer는 React의 함수형 컴포넌트를 렌더 하고자 하는 데이터가 결합한 형태로 전환합니다. MobX를 사용하면 smart, dumb 컴포넌트의 구분이 없습니다. smart 한 형태로 렌터를 하지만, dumb의 형태로 정의됩니다. MobX는 단순히 필요할 때 컴포넌트가 다시 렌더링 되도록 하지만, 그 이상의 것은 없습니다. 위의 코드를 살펴보면 onClick 핸들러는 TodoView 컴포넌트를 리렌더합니다. 그리고, unfinished task의 개수가 변경되면 TodoListView 또한 리렌더합니다. 하지만, Tasks left: 줄을 제거하거나 별도의 컴포넌트로 분리하면 TodoListView는 todo의 finished 속성이 toggle 되어도 다시 렌더가 되지 않습니다.

Custom reactions

Custom reaction은 autorun, reaction, when 함수를 이용해 생성해 구체적인 상황에 맞게 구성할 수 있습니다. 아래의 예제는 autorun 함수를 이용해 unfinishedTodoCount가 변경될 때마다 log message를 프린트 합니다.

autorun(() => {
  console.log(`Tasks left: ${todos.unfinishedTodoCount}`);
});

What will MobX react to?

왜 unfinishedTodoCount가 변경될 때마다 새로운 메세지가 프린트될까요? 그에 대한 간단한 답변은 아래와 같습니다.

MobX는 추적하고 있는 함수 실행 중 만나는 observable 속성에 대해 반응합니다.

MobX가 어떠한 observable에 반응하는지 자세히 확인하고 싶으시면 understanding what MobX reacts to를 확인해 주세요.

Actions

Egghead.io lesson 5: actions

다른 많은 flux framework와 다르게, MobX는 user event를 어떻게 핸들링 해야하는지에 대한 뚜렷한 규정이 없습니다.

  • Flux와 같은 방식으로 처리할 수 있습니다.
  • 또는, RxJS를 이용할 수도 있습니다.
  • 또는, 위 예제의 onClick 핸들러와 같이 매우 간단한 방식으로 처리할 수도 있습니다.

결론적으로, 어떻게든 state를 업데이트 할 수 있습니다.

state를 업데이트하면, 나머지 과정은 MobX가 효율적인 방법으로 처리합니다. 따라서, 아래와 같이 단순한 코드도 user interface를 업데이트 할 수 있습니다.

이벤트를 만들기 위해 dispatcher 등과 같은 어떠한 기술적인 요구가 없습니다. 결국, React Component는 state를 나타내는 역할만 하게 됩니다.

store.todos.push(new Todo('Get Coffee'), new Todo('Write simple code'));
store.todos[0].finished = true;

그렇지만, MobX는 선택적으로 사용할 수 있는 action에 대한 concept이 있습니다. asynchronous action을 쓰는 방법에 대해 알고 싶다면, 위의 글을 참고해 주세요. 코드를 구조화하고 언제, 어디에서 state를 수정할지에 대해 알 수 있을 것입니다.

MobX: Simple and scalable

MobX는 단순하면서도 확장성이 좋은 state management 라이브러리입니다.

Using classes and real references

MobX를 이용하면 정규화할 필요가 없으며, 복잡한 도메인 모델에 적합합니다.

Referential integrity is guaranteed

데이터 정규화가 필요 없기 때문에, MobX는 state와 derivation의 관계를 자동으로 추적합니다. MobX는 이러한 관계에 대한 온전함을 보증합니다.

Simpler actions are easier to maintain

앞서 설명한 바와 같이, MobX에서 state를 수정하기가 매우 쉽습니다. 단순히 의도하는 대로 코드를 작성하면, MobX가 나머지를 처리해 줄 것입니다.

Fine grained observability is efficient

MobX는 최적의 연산을 위해 application state에서 파생된 값을 graph로 생성합니다. "모든 것을 파생한다"는 개념은 비용이 높다고 여겨질 수 있습니다. MobX는 가상의 파생된 값을 이용해 Graph를 만듭니다. 그리고 실제 state와의 동기화를 위해 최적의 연산만 하도록 설계되어 있습니다.

실제 MobX를 'Mendix'에서 테스트했을 때, 직접 action을 생성하거나 container component를 이용했을 때보다 MobX를 사용하는 것이 더 효율적이었습니다.

이는 일반적으로 MobX가 더 정교한 리스너를 생성하기 때문입니다. 그리고, MobX는 파생된 값의 인과 관계를 파악해 파생된 값이 중복되어 실행되거나 어떠한 결함을 만들지 않도록 합니다.

이에 대한 자세한 내용은 in-depth explanation of MobX에서 확인할 수 있습니다.

Easy interoperability

MobX는 기본 자바스크립트 기반으로 작동되며, 다른 라이브러리와도 특별한 설정 없이 사용할 수 있습니다.

우리는 기존에 사용하던 react-router, director, superagent, lodash 등과 같은 라이브러리를 계속 사용할 수 있습니다.

같은 이유로 동일 구조의 애플리케이션이나 React-native에서 서버와 클라이언트에서 모두 사용할 수 있습니다.

이러한 이유로 다른 상태관리 라이브러리에 비해 저 적은 개념만 파악하면 MobX를 이용할 수 있습니다.