MobX

MobX

  • API 참고서
  • 영어 문서
  • 후원자
  • GitHub

›MobX core

Introduction

  • MobX에 대하여
  • MobX 문서에 대하여
  • 설치 방법
  • MobX의 요지

MobX core

  • Observable state
  • Actions
  • Computeds
  • Reactions {🚀}

MobX and React

  • React 통합
  • React 최적화 {🚀}

Tips & Tricks

  • 데이터 스토어 정의
  • 반응성 이해하기
  • 서브클래싱
  • 반응 분석하기 {🚀}
  • 인수가 필요한 computed {🚀}
  • MobX-utils {🚀}
  • Custom observables {🚀}
  • Lazy observables {🚀}
  • Collection utilities {🚀}
  • Intercept & Observe {🚀}

Fine-tuning

  • 환경설정 {🚀}
  • 데코레이터 사용하기 {🚀}
  • MobX 4 또는 5에서 마이그레이션 하기 {🚀}
Edit

action을 이용한 state 업데이트

사용 방법:

  • action 주석(annotation)
  • action(fn)
  • action(name, fn)

모든 애플리케이션에는 action이 있습니다. action은 state를 수정하는 코드 조각입니다. 원칙적으로 action은 항상 이벤트에 대한 응답으로 발생합니다. 예를 들어 버튼 클릭, 일부 입력 변경, 웹 소켓 메시지 도착 등이 있습니다.

makeAutoObservable를 통해 대부분의 action을 자동화 할 수 있지만, action을 따로 선언해야 합니다. 왜냐하면 이를 통해 코드 구조를 개선할 수 있고 아래와 같은 성능 이점을 제공하기 때문입니다.

  1. 트랜잭션(transaction) 내부에서 실행됩니다. 가장 바깥쪽 action이 완료될 때까지 reaction은 실행되지 않기 때문에, action 실행 중에 생성된 중간값 또는 불완전한 값은 action이 완료될 때까지 애플리케이션에서 볼 수 없습니다.

  2. 기본적으로 action 외부에서 state를 변경할 수 없습니다. 이를 통해 코드에서 state 업데이트가 발생하는 위치를 명확히 확인할 수 있습니다.

action 주석은 state를 수정하려는 함수에서만 사용해야 합니다. 정보(조회 또는 데이터 필터링)를 유도하는 함수는 MobX가 추적할 수 있도록 action으로 표시하면 안 됩니다. action 주석이 달린 멤버는 non-enumerable이 됩니다.

예시

makeObservable
makeAutoObservable
action.bound
action(fn)
runInAction(fn)
import { makeObservable, observable, action } from "mobx"

class Doubler {
value = 0

constructor(value) {
makeObservable(this, {
value: observable,
increment: action
})
}

increment() {
// 중간 state는 observer에게 표시되지 않습니다.
this.value++
this.value++
}
}
import { makeAutoObservable } from "mobx"

class Doubler {
value = 0

constructor(value) {
makeAutoObservable(this)
}

increment() {
this.value++
this.value++
}
}
import { makeObservable, observable, action } from "mobx"

class Doubler {
value = 0

constructor(value) {
makeObservable(this, {
value: observable,
increment: action.bound
})
}

increment() {
this.value++
this.value++
}
}

const doubler = new Doubler()

// 해당 방법으로 increment를 호출하는 것은 이미 바인딩되어 있으므로 안전합니다.
setInterval(doubler.increment, 1000)
import { observable, action } from "mobx"

const state = observable({ value: 0 })

const increment = action(state => {
state.value++
state.value++
})

increment(state)
import { observable, runInAction } from "mobx"

const state = observable({ value: 0 })

runInAction(() => {
state.value++
state.value++
})

action 함수를 이용한 함수 래핑

MobX의 트랜잭션 특성을 최대한 활용하려면 action을 최대한 외부로 전달해야 합니다. state를 수정하려는 경우 클래스 메서드를 action으로 표시하는 것이 좋습니다. 이벤트 핸들러는 가장 바깥쪽 트랜잭션이기 때문에 action으로 표시하는 것이 더 좋습니다. 그리고 action 주석이 표시되지 않은 단일 이벤트 핸들러가 이후에 두 개의 action을 호출하는 경우 똑같이 두 개의 트랜잭션을 생성합니다.

action 기반 이벤트 핸들러를 생성하기 위해 action은 주석 기능뿐만 아니라 고차 함수도 될 수 있습니다. 함수를 인수로 사용하여 호출할 수 있으며, 이러한 경우 동일한 특징을 가진 action 래핑 함수를 반환합니다.

예를 들어 React에서 onClick 핸들러는 아래와 같이 래핑 될 수 있습니다.

const ResetButton = ({ formState }) => (
    <button
        onClick={action(e => {
            formState.resetPendingUploads()
            formState.resetValues()
            e.stopPropagation()
        })}
    >
        Reset form
    </button>
)

디버깅 목적으로 래핑 된 함수의 이름을 지정하거나 action에 대한 첫 번째 인수로 이름을 전달하는 것이 좋습니다.

메모: action은 추적되지 않습니다.

action의 또 다른 특징은 추적되지 않는다는 것입니다. action이 부수효과 내부 또는 computed 값 내부에서 호출되면 action에서 읽은 observable 항목은 derivation의 종속성으로 계산되지 않습니다.

makeAutoObservable, extendObservable 및 observable은 autoAction이라는 특별한 action을 사용합니다. 이 동작은 함수가 derivation인지 action인지를 런타임에 결정합니다.

action.bound

사용 방법:

  • action.bound 주석(annotation)

action.bound 주석은 this가 항상 함수 내에서 적절하게 바인딩 될 수 있도록 메서드를 올바른 인스턴스에 바인딩하는 데 사용됩니다.

Tip: 모든 action과 flow를 자동으로 바인딩하려면 makeAutoObservable(o, {}, { autoBind: true })을 사용하세요.

import { makeAutoObservable } from "mobx"

class Doubler {
    value = 0

    constructor(value) {
        makeAutoObservable(this, {}, { autoBind: true })
    }

    increment() {
        this.value++
        this.value++
    }

    *flow() {
        const response = yield fetch("http://example.com/value")
        this.value = yield response.json()
    }
}

runInAction

사용 방법:

  • runInAction(fn)

해당 유틸리티를 사용하여 즉시 호출되는 임시 action을 생성할 수 있습니다. 비동기 프로세스에서 유용하게 사용하실 수 있습니다. 예를 들어 위의 코드 블록을 확인하세요.

action 및 상속

프로토타입에서 정의된 action만 서브클래스에서 재정의(override)할 수 있습니다.

class Parent {
    // on instance
    arrowAction = () => {}

    // on prototype
    action() {}
    boundAction() {}

    constructor() {
        makeObservable(this, {
            arrowAction: action,
            action: action,
            boundAction: action.bound,
        })
    }
}
class Child extends Parent {
    // THROWS: TypeError: Cannot redefine property: arrowAction
    arrowAction = () => {}

    // OK
    action() {}
    boundAction() {}

    constructor() {
        super()
        makeObservable(this, {
            arrowAction: override,
            action: override,
            boundAction: override,
        })
    }
}

this를 단일 action에 바인딩하기 위해 화살표 함수 대신 action.bound를 사용할 수 있습니다.
자세한 내용은 서브클래싱을 확인하세요.

비동기 action

모든 reaction은 발생 시점과 관계없이 자동으로 업데이트되므로, 본질적으로 MobX에서는 비동기 프로세스에 대해 특별한 처리가 필요하지 않습니다. 그리고 observable 객체는 mutable 하므로 action이 실행되는 동안 observable 객체에 대한 참조를 유지하는 것이 안전합니다. 하지만 비동기 프로세스에서 observable을 업데이트하는 모든 단계는 action으로 표시되어야 합니다. 위 API를 활용하면 아래와 같이 다양한 방법으로 action을 표시할 수 있습니다.

예를 들어 프라미스를 처리할 때 state를 업데이트하는 핸들러는 아래와 같이 action을 사용하여 래핑하거나 action이 되어야 합니다.

`action`으로 핸들러 감싸기
별도의 action을 통한 업데이트 처리
async/await + runInAction
`flow` + generator function

프라미스 해결 핸들러는 인라인으로 처리되지만 원래 작업이 완료된 후에 실행되므로 action으로 래핑해야 합니다.

import { action, makeAutoObservable } from "mobx"

class Store {
githubProjects = []
state = "pending" // "pending", "done" or "error"

constructor() {
makeAutoObservable(this)
}

fetchProjects() {
this.githubProjects = []
this.state = "pending"
fetchGithubProjectsSomehow().then(
action("fetchSuccess", projects => {
const filteredProjects = somePreprocessing(projects)
this.githubProjects = filteredProjects
this.state = "done"
}),
action("fetchError", error => {
this.state = "error"
})
)
}
}

프라미스 핸들러가 클래스 필드라면 makeAutoObservable에 의해 action이 자동으로 래핑 됩니다.

import { makeAutoObservable } from "mobx"

class Store {
githubProjects = []
state = "pending" // "pending", "done" or "error"

constructor() {
makeAutoObservable(this)
}

fetchProjects() {
this.githubProjects = []
this.state = "pending"
fetchGithubProjectsSomehow().then(this.projectsFetchSuccess, this.projectsFetchFailure)
}

projectsFetchSuccess = projects => {
const filteredProjects = somePreprocessing(projects)
this.githubProjects = filteredProjects
this.state = "done"
}

projectsFetchFailure = error => {
this.state = "error"
}
}

await 이후 단계가 동일하지 않기 때문에 action 래핑이 필요합니다. 여기에서 runInAction을 활용할 수 있습니다.

import { runInAction, makeAutoObservable } from "mobx"

class Store {
githubProjects = []
state = "pending" // "pending", "done" or "error"

constructor() {
makeAutoObservable(this)
}

async fetchProjects() {
this.githubProjects = []
this.state = "pending"
try {
const projects = await fetchGithubProjectsSomehow()
const filteredProjects = somePreprocessing(projects)
runInAction(() => {
this.githubProjects = filteredProjects
this.state = "done"
})
} catch (e) {
runInAction(() => {
this.state = "error"
})
}
}
}
import { flow, makeAutoObservable, flowResult } from "mobx"

class Store {
githubProjects = []
state = "pending"

constructor() {
makeAutoObservable(this, {
fetchProjects: flow
})
}

// 별 모양은 제너레이터(generator) 함수입니다!
*fetchProjects() {
this.githubProjects = []
this.state = "pending"
try {
// await 대신 yield를 사용합니다.
const projects = yield fetchGithubProjectsSomehow()
const filteredProjects = somePreprocessing(projects)
this.state = "done"
this.githubProjects = filteredProjects
} catch (error) {
this.state = "error"
}
}
}

const store = new Store()
const projects = await flowResult(store.fetchProjects())

async·await 대신에 flow 사용하기 {🚀}

사용 방법:

  • flow 주석(annotation)
  • flow(function* (args) { })

flow 래퍼는 MobX action을 더 쉽게 작업할 수 있게 async·await 대신 사용할 수 있는 옵션입니다. flow는 generator function을 유일한 입력으로 사용합니다. generator 안에서 yield를 사용하여(await somePromise 대신 yield somePromise) 프라미스를 연결할 수 있습니다. flow 매커니즘은 프라미스가 해결될 때 generator가 계속 진행되거나 throw되는지 확인합니다.

따라서 flow는 async·await과 달리 action 래핑이 필요하지 않습니다. 이는 다음과 같이 적용될 수 있습니다.

  1. 비동기 함수를 flow로 감쌉니다.
  2. async 대신 function *을 사용합니다.
  3. await 대신 yield를 사용합니다.

flow + generator function 예제에서 실제로 어떻게 사용하는지 확인해보세요.

flowResult 함수는 TypeScript를 사용할 때만 필요합니다. 메서드를 flow로 데코레이팅하기 때문에 반환된 generator는 프라미스로 래핑 됩니다. 그러나 TypeScript는 이러한 변환을 인식하지 못하기 때문에 flowResult를 사용하여 해당 변경을 인식할 수 있도록 해야 합니다.

makeAutoObservable, makeObservable, extendObservable, observable(object) 등 observable 필드를 생성하는 api는 자동으로 generator를 flow로 유추합니다. flow 주석이 달린 멤버는 non-enumerable이 됩니다.

{🚀} Note: 객체 필드에 flow 사용하기 flow는 action과 마찬가지로 함수를 직접 래핑할 수 있습니다. 위의 예시는 아래와 같이 작성될 수 있습니다.

import { flow } from "mobx"

class Store {
    githubProjects = []
    state = "pending"

    fetchProjects = flow(function* (this: Store) {
        this.githubProjects = []
        this.state = "pending"
        try {
            // await 대신에 yield를 사용합니다.
            const projects = yield fetchGithubProjectsSomehow()
            const filteredProjects = somePreprocessing(projects)
            this.state = "done"
            this.githubProjects = filteredProjects
        } catch (error) {
            this.state = "error"
        }
    })
}

const store = new Store()
const projects = await store.fetchProjects()

flowResult가 더 이상 필요하지 않다는 장점이 있고, 타입을 올바르게 추론하기 위해 this가 필요하다는 단점이 있습니다.

flow.bound

사용 방법:

  • flow.bound 주석(annotation)

flow.bound 주석은 this가 항상 함수 내에서 적절하게 바인딩 될 수 있도록 메서드를 올바른 인스턴스에 바인딩하는 데 사용됩니다. action과 유사하게 flow는 autoBind 옵션을 사용하여 기본적으로 바인딩 할 수 있습니다.

flow 취소 {🚀}

flow의 또 다른 장점은 취소할 수 있다는 것입니다. flow의 반환 값은 generator 함수에서 반환된 값을 해결(resolve)하는 프라미스입니다. 반환된 프라미스는 실행 중인 generator를 취소할 수 있는 cancel() 메서드가 있습니다. 모든 try·finally 절은 계속 실행됩니다.

필수 action 비활성화 {🚀}

기본적으로 MobX 6 버전 이상에서는 action을 사용하여 state를 변경해야 합니다. 이러한 동작을 비활성화시키고 싶다면, enforceActions 섹션을 확인해주세요. 이러한 옵션은 경고의 가치가 덜 중요할 수도 있는 단위 테스트에서 유용할 수 있습니다.

← Observable stateComputeds →
  • 예시
  • action 함수를 이용한 함수 래핑
  • action.bound
  • runInAction
  • action 및 상속
  • 비동기 action
  • async·await 대신에 flow 사용하기 {🚀}
  • flow.bound
  • flow 취소 {🚀}
  • 필수 action 비활성화 {🚀}
MobX
Docs
About MobXThe gist of MobX
Community
GitHub discussions (NEW)Stack Overflow
More
Star