인터페이스는 현지화되어 있으며, Skill 소개와 설명 문서는 선택한 언어로 AI가 자동 번역합니다. 코드·명령어·경로·전문 용어는 원문을 유지하며 원작자 문서가 우선합니다.
Skills 둘러보기
생산성 및 협업 · SKILL.md

mobx

mobx

MobX is a state management library for JavaScript and React that makes state observable, so computed values and components update automatically when it changes. Use when a user asks to add MobX to a React app, create a store with makeAutoObservable, wrap components in observer, write computed values, actions, reactions or flow, fix a MobX component that doe…

Independent GitHub snapshot · Automated classification · Original documentation governs

Tterminalskills / skillsApache-2.0

Skill 문서

GitHub

name: mobx description: >- MobX is a state management library for JavaScript and React that makes state observable, so computed values and components update automatically when it changes. Use when a user asks to add MobX to a React app, create a store with makeAutoObservable, wrap components in observer, write computed values, actions, reactions or flow, fix a MobX component that does not re-render, or migrate from MobX 6 to MobX 7. license: Apache-2.0 compatibility: 'MobX 7 with mobx-react-lite 5 (React 18 or 19); needs native Proxy support' metadata: author: terminal-skills version: 1.1.0 category: development repository: https://github.com/mobxjs/mobx tags: - react - state-management - observable - reactive - proxy

MobX — Reactive State Management

Overview

MobX is a simple and scalable state management library based on transparent reactive programming. State is made observable, derived values are computed from it, actions mutate it, and reactions run side effects — the UI updates automatically when the state it read changes, without manual subscriptions. Stores can be classes or plain objects.

Current major version: MobX 7 (July 2026) with mobx-react-lite 5 for function components and mobx-react 10 for class components. MobX 7 is a cleanup release: Proxy-only, Stage 3 decorators only, and several namespaced APIs became named exports (see "MobX 7 changes" below).

Instructions

Installation

npm install mobx mobx-react-lite

Use mobx-react instead of mobx-react-lite only when class components or the @observer class decorator are needed. With TypeScript, set "useDefineForClassFields": true in tsconfig.json; without spec-compliant class fields, a field declared without an initial value (user?: User;) cannot be made observable.

Observable Store

// stores/todo-store.ts
import { makeAutoObservable, runInAction } from "mobx";

export interface Todo { id: string; text: string; done: boolean }

export class TodoStore {
  todos: Todo[] = [];
  filter: "all" | "active" | "done" = "all";
  isLoading = false;

  constructor() {
    // Infers observables, computeds (getters), actions (methods), flows (generators).
    // autoBind lets methods be passed around as callbacks: onClick={store.addTodo}
    makeAutoObservable(this, {}, { autoBind: true });
  }

  // Computed (cached, recalculated only when dependencies change)
  get filteredTodos() {
    switch (this.filter) {
      case "active": return this.todos.filter(t => !t.done);
      case "done": return this.todos.filter(t => t.done);
      default: return this.todos;
    }
  }

  get remaining() {
    return this.todos.filter(t => !t.done).length;
  }

  // Actions (state mutations)
  addTodo(text: string) {
    this.todos.push({ id: crypto.randomUUID(), text, done: false });
  }

  toggleTodo(id: string) {
    const todo = this.todos.find(t => t.id === id);
    if (todo) todo.done = !todo.done;     // Direct mutation — MobX tracks it
  }

  removeTodo(id: string) {
    this.todos = this.todos.filter(t => t.id !== id);
  }

  // Async action: code after an await is no longer inside the action
  async fetchTodos() {
    this.isLoading = true;
    try {
      const response = await fetch("/api/todos");
      const data: Todo[] = await response.json();
      runInAction(() => { this.todos = data; });
    } finally {
      runInAction(() => { this.isLoading = false; });
    }
  }

  // Same thing as a flow: a generator needs no runInAction after each yield
  *syncTodos(): Generator<Promise<unknown>, number, any> {
    this.isLoading = true;
    try {
      const response = yield fetch("/api/todos");
      this.todos = yield response.json();
      return this.todos.length;
    } finally {
      this.isLoading = false;
    }
  }
}

Call a flow like a normal method; in TypeScript wrap it to get a typed promise: const count = await flowResult(store.syncTodos()).

Observer Components

import { createContext, useContext } from "react";
import { observer, useLocalObservable } from "mobx-react-lite";
import { TodoStore } from "./stores/todo-store";

const StoreContext = createContext<TodoStore | null>(null);
const useStore = () => {
  const store = useContext(StoreContext);
  if (!store) throw new Error("StoreContext.Provider is missing");
  return store;
};

// observer tracks which observables the render read and re-renders only for those
const TodoList = observer(() => {
  const store = useStore();
  if (store.isLoading) return <p>Loading…</p>;
  return (
    <div>
      <p>{store.remaining} remaining</p>
      <ul>
        {store.filteredTodos.map(t => (
          <li key={t.id} onClick={() => store.toggleTodo(t.id)}
            style={{ textDecoration: t.done ? "line-through" : "none" }}>
            {t.text}
          </li>
        ))}
      </ul>
    </div>
  );
});

// Component-local observable state
const Counter = observer(() => {
  const state = useLocalObservable(() => ({ count: 0, increment() { this.count++; } }));
  return <button onClick={state.increment}>Clicked {state.count} times</button>;
});

export const App = ({ store }: { store: TodoStore }) => (
  <StoreContext.Provider value={store}><TodoList /><Counter /></StoreContext.Provider>
);

Reactions

import { autorun, reaction, when } from "mobx";

// reaction: runs the effect when the tracked expression changes (not on creation)
const dispose = reaction(
  () => store.remaining,
  (remaining, previous) => { document.title = `${remaining} todos left (was ${previous})`; },
);

// autorun: runs once immediately, then whenever anything it read changes
const stopSaving = autorun(() => {
  localStorage.setItem("todos", JSON.stringify(store.todos));
});

await when(() => !store.isLoading);   // promise that resolves once the condition is true

dispose(); stopSaving();              // always dispose reactions you no longer need

MobX 7 changes (upgrading from MobX 6)

MobX 6MobX 7
observable.ref / .shallow / .deep / .structobservableRef, observableShallow, observableDeep, observableStruct
computed.struct, action.bound, flow.boundcomputedStruct, actionBound, flowBound
comparer.structural / .shallow / .default / .identitycompareStructural, compareShallow, compareDefault, compareIdentity
configure({ useProxies }), { proxy: false }removed — Proxy is always used
trace()removed — use getDependencyTree, getObserverTree, spy
Legacy decorators (experimentalDecorators)Stage 3 decorators only: @observable accessor title = "", no makeObservable(this)
Provider, inject (mobx-react)React.createContext
useLocalStore, useObserver, useStaticRenderinguseLocalObservable, observer / <Observer>, enableStaticRendering

mobx-react-lite 5 and mobx-react 10 require MobX 7 and React 18+. Projects that must keep legacy decorators stay on MobX 6.

Examples

Example 1: Cart store with decorators

User request: "Add a MobX cart store to our checkout — the total should update when items or the coupon change."

Stage 3 decorators need TypeScript 5+ with experimentalDecorators off (or Babel's @babel/plugin-proposal-decorators with version: "2023-05").

import { observable, computed, action, actionBound, autorun } from "mobx";

class CartStore {
  @observable accessor items: { sku: string; price: number; qty: number }[] = [];
  @observable accessor coupon: string | null = null;

  @computed get total() {
    const sum = this.items.reduce((s, i) => s + i.price * i.qty, 0);
    return this.coupon === "WELCOME10" ? sum * 0.9 : sum;
  }

  @action add(sku: string, price: number, qty = 1) {
    this.items.push({ sku, price, qty });
  }

  @actionBound applyCoupon(code: string) { this.coupon = code; }
}

const cart = new CartStore();
const stop = autorun(() => console.log("total", cart.total));
cart.add("TSHIRT-M", 24, 2);
cart.applyCoupon("WELCOME10");
stop();

Output — the autorun fires once at start and once per change:

total 0
total 48
total 43.2

Example 2: Upgrade a store from MobX 6 to MobX 7

User request: "We bumped mobx to 7 and the session store throws on observable.ref. Fix it."

npm install mobx@7 mobx-react-lite@5
// Before (MobX 6): makeObservable(this, { user: observable.ref, signIn: action.bound })
import { makeObservable, observableRef, actionBound, autorun } from "mobx";

class SessionStore {
  user: { id: string; name: string } | null = null;

  constructor() {
    makeObservable(this, { user: observableRef, signIn: actionBound });
  }

  signIn(user: { id: string; name: string }) { this.user = user; }
}

const session = new SessionStore();
autorun(() => console.log("user", session.user?.name));
session.signIn({ id: "u_81", name: "Priya Nair" });

Output: user undefined, then user Priya Nair. Also remove useProxies from any configure() call and replace Provider/inject with a React context as shown above.

Guidelines

  1. makeAutoObservable — Use in the constructor; it makes properties observable, getters computed, methods actions and generators flows. It cannot be used in classes that extend another class or are subclassed — use makeObservable with explicit annotations there
  2. observer() — Wrap every React component that reads observables with observer; it only re-renders when the observables it read change
  3. Direct mutations — Mutate state directly in actions (this.todos.push(...)) — MobX uses Proxy to track changes
  4. runInAction or flow — State changes after an await must be wrapped in runInAction(), or write the method as a generator (flow). Changing observed state outside an action logs a strict-mode warning (enforceActions: "observed" is the default)
  5. Computed values — Use getters for derived data; MobX caches results and recalculates only when dependencies change. Keep them free of side effects
  6. Reactions for side effects — Use reaction() or autorun() for logging, localStorage sync, API calls on state change, and call the returned disposer when done; inside components create them in useEffect and return the disposer
  7. Small stores — Create multiple domain stores (AuthStore, CartStore, UIStore); share them through React context or a module import
  8. Don't destructure too early — const { count } = store outside an observer component or reaction copies the value and loses tracking; read store.count where it is used
  9. Serializing — Use toJS(store.todos) to get a plain, non-observable copy. Fields declared with @observable accessor are not enumerable, so JSON.stringify(store) and Object.keys(store) skip them unless the class defines toJSON
  10. When not to use it — MobX 7 needs native Proxy, so it cannot run on engines without it; for a couple of local values plain React state is enough
저장한 Skills