MobX Complete Guide | Simple Reactive State Management
이 글의 핵심
MobX is a simple, scalable state management solution that makes state management transparent through reactive programming. It automatically tracks dependencies and updates components.
Introduction
MobX is a battle-tested library that makes state management simple and scalable by transparently applying functional reactive programming (FRP). The philosophy is simple: anything that can be derived from the application state, should be derived automatically.
The Problem
Traditional state management:
// Redux: Too much boilerplate
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
default:
return state;
}
};
// Context: Re-renders entire tree
const [state, setState] = useState({ count: 0, name: '' });
// Changing count re-renders all consumers
The Solution
With MobX:
import { makeObservable, observable, action } from 'mobx';
class Store {
count = 0;
constructor() {
makeObservable(this, {
count: observable,
increment: action,
});
}
increment() {
this.count++;
}
}
Real-World Adoption
MobX is battle-tested in production at scale:
Enterprise Usage:
- Microsoft: Uses MobX in Microsoft Teams and Office 365 web apps for managing complex collaborative state
- Coinbase: Powers their cryptocurrency trading platform with MobX for real-time price updates and order management
- Amazon AWS Console: Employs MobX for managing state in AWS service dashboards
- SAP: Utilizes MobX in their Fiori UI framework for enterprise resource planning applications
Market Position:
- 2.6+ million weekly npm downloads (April 2026) - consistently high adoption
- 27,000+ GitHub stars - one of the most popular React state management libraries
- Used in 250,000+ public GitHub repositories
- MobX 6 (current version) introduced modern ES6+ decorators and improved TypeScript support
Why Companies Choose MobX:
- Minimal boilerplate: 5-10x less code compared to Redux for the same functionality
- Automatic tracking: No manual subscription management required
- Performance: Fine-grained reactivity means only affected components re-render
- OOP-friendly: Fits naturally with class-based architectures common in enterprise apps
- Framework agnostic: Works with React, Vue, Angular, or vanilla JavaScript
Community & Ecosystem:
- Maintained by Michel Weststrate (creator) and active contributors
- Official bindings for React (
mobx-react-lite), Angular, Vue - Rich ecosystem of tools: MobX-State-Tree (opinionated), MobX-Utils, DevTools
MobX’s “invisible state management” approach makes it ideal for teams that want to focus on business logic rather than boilerplate code.
1. Installation
npm install mobx mobx-react-lite
2. Core Concepts
Observable State
import { makeObservable, observable, action } from 'mobx';
class TodoStore {
todos = [];
constructor() {
makeObservable(this, {
todos: observable,
addTodo: action,
});
}
addTodo(text) {
this.todos.push({ id: Date.now(), text, done: false });
}
}
With makeAutoObservable (Simpler)
import { makeAutoObservable } from 'mobx';
class TodoStore {
todos = [];
constructor() {
makeAutoObservable(this); // Automatically marks everything
}
addTodo(text) {
this.todos.push({ id: Date.now(), text, done: false });
}
get completedCount() {
return this.todos.filter(t => t.done).length;
}
}
3. React Integration
observer Component
import { observer } from 'mobx-react-lite';
import { todoStore } from './stores';
const TodoList = observer(() => {
return (
<div>
{todoStore.todos.map(todo => (
<div key={todo.id}>{todo.text}</div>
))}
</div>
);
});
Creating Store Context
// stores/TodoStore.ts
import { makeAutoObservable } from 'mobx';
class TodoStore {
todos = [];
constructor() {
makeAutoObservable(this);
}
addTodo(text: string) {
this.todos.push({ id: Date.now(), text, done: false });
}
toggleTodo(id: number) {
const todo = this.todos.find(t => t.id === id);
if (todo) todo.done = !todo.done;
}
}
export const todoStore = new TodoStore();
// App.tsx
import { observer } from 'mobx-react-lite';
import { todoStore } from './stores/TodoStore';
const App = observer(() => {
const [text, setText] = useState('');
const handleAdd = () => {
todoStore.addTodo(text);
setText('');
};
return (
<div>
<input value={text} onChange={e => setText(e.target.value)} />
<button onClick={handleAdd}>Add</button>
<ul>
{todoStore.todos.map(todo => (
<li key={todo.id} onClick={() => todoStore.toggleTodo(todo.id)}>
{todo.done ? '✅' : '⬜'} {todo.text}
</li>
))}
</ul>
</div>
);
});
4. Computed Values
import { makeAutoObservable, computed } from 'mobx';
class CartStore {
items = [];
constructor() {
makeAutoObservable(this);
}
get total() {
return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
get itemCount() {
return this.items.reduce((sum, item) => sum + item.quantity, 0);
}
}
Computed values are cached:
const cart = new CartStore();
cart.items.push({ price: 10, quantity: 2 });
console.log(cart.total); // Computes: 20
console.log(cart.total); // Cached: 20 (doesn't recompute)
cart.items[0].quantity = 3;
console.log(cart.total); // Recomputes: 30
5. Actions
Synchronous Actions
import { makeAutoObservable, action } from 'mobx';
class UserStore {
users = [];
constructor() {
makeAutoObservable(this);
}
addUser(user) {
this.users.push(user);
}
removeUser(id) {
this.users = this.users.filter(u => u.id !== id);
}
}
Async Actions (runInAction)
import { makeAutoObservable, runInAction } from 'mobx';
class UserStore {
users = [];
loading = false;
constructor() {
makeAutoObservable(this);
}
async fetchUsers() {
this.loading = true;
try {
const res = await fetch('/api/users');
const users = await res.json();
runInAction(() => {
this.users = users;
this.loading = false;
});
} catch (error) {
runInAction(() => {
this.loading = false;
});
}
}
}
flow (Alternative for Async)
import { makeAutoObservable, flow } from 'mobx';
class UserStore {
users = [];
loading = false;
constructor() {
makeAutoObservable(this, {
fetchUsers: flow,
});
}
*fetchUsers() {
this.loading = true;
try {
const res = yield fetch('/api/users');
const users = yield res.json();
this.users = users;
} finally {
this.loading = false;
}
}
}
6. Reactions
autorun
import { autorun } from 'mobx';
const store = new TodoStore();
autorun(() => {
console.log('Total todos:', store.todos.length);
});
store.addTodo('Learn MobX'); // Logs: Total todos: 1
reaction
import { reaction } from 'mobx';
reaction(
() => store.todos.length, // What to track
(count) => {
console.log('Todo count changed:', count);
}
);
when
import { when } from 'mobx';
when(
() => store.todos.length > 5,
() => console.log('More than 5 todos!')
);
7. Real-World Example: Shopping Cart
// stores/CartStore.ts
import { makeAutoObservable } from 'mobx';
interface CartItem {
id: number;
name: string;
price: number;
quantity: number;
}
class CartStore {
items: CartItem[] = [];
constructor() {
makeAutoObservable(this);
}
addItem(product: { id: number; name: string; price: number }) {
const existing = this.items.find(item => item.id === product.id);
if (existing) {
existing.quantity++;
} else {
this.items.push({ ...product, quantity: 1 });
}
}
removeItem(id: number) {
this.items = this.items.filter(item => item.id !== id);
}
updateQuantity(id: number, quantity: number) {
const item = this.items.find(item => item.id === id);
if (item) {
item.quantity = quantity;
}
}
get total() {
return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
get itemCount() {
return this.items.reduce((sum, item) => sum + item.quantity, 0);
}
clear() {
this.items = [];
}
}
export const cartStore = new CartStore();
// components/Cart.tsx
import { observer } from 'mobx-react-lite';
import { cartStore } from '../stores/CartStore';
export const Cart = observer(() => {
return (
<div>
<h2>Shopping Cart ({cartStore.itemCount} items)</h2>
{cartStore.items.map(item => (
<div key={item.id}>
<span>{item.name}</span>
<span>${item.price}</span>
<input
type="number"
value={item.quantity}
onChange={e => cartStore.updateQuantity(item.id, +e.target.value)}
/>
<button onClick={() => cartStore.removeItem(item.id)}>Remove</button>
</div>
))}
<div>Total: ${cartStore.total.toFixed(2)}</div>
<button onClick={() => cartStore.clear()}>Clear Cart</button>
</div>
);
});
8. Multiple Stores
// stores/RootStore.ts
import { makeAutoObservable } from 'mobx';
import { UserStore } from './UserStore';
import { CartStore } from './CartStore';
class RootStore {
userStore: UserStore;
cartStore: CartStore;
constructor() {
this.userStore = new UserStore(this);
this.cartStore = new CartStore(this);
makeAutoObservable(this);
}
}
export const rootStore = new RootStore();
// Using React Context
import { createContext, useContext } from 'react';
import { rootStore } from './stores/RootStore';
const StoreContext = createContext(rootStore);
export const useStore = () => useContext(StoreContext);
// In component
const { userStore, cartStore } = useStore();
9. Best Practices
1. Use makeAutoObservable
// Good
class Store {
count = 0;
constructor() {
makeAutoObservable(this);
}
}
// Verbose (avoid unless needed)
class Store {
count = 0;
constructor() {
makeObservable(this, {
count: observable,
increment: action,
});
}
}
2. Use runInAction for Async
// Good
async fetchData() {
const data = await api.getData();
runInAction(() => {
this.data = data;
});
}
// Bad: modifying state outside action
async fetchData() {
const data = await api.getData();
this.data = data; // Warning!
}
3. Avoid Mutating Arrays Directly
// Good
addItem(item) {
this.items.push(item);
}
removeItem(id) {
this.items = this.items.filter(i => i.id !== id);
}
// Also good (with MobX)
removeItem(id) {
this.items.splice(
this.items.findIndex(i => i.id === id),
1
);
}
10. Performance Tips
1. Use observer on Component Level
// Good: Only this component re-renders
const TodoItem = observer(({ todo }) => {
return <div>{todo.text}</div>;
});
// Bad: Parent re-renders all children
const TodoList = observer(() => {
return store.todos.map(todo => <TodoItem todo={todo} />);
});
2. Dereference Values Late
// Good: Only re-renders when name changes
const UserName = observer(({ user }) => {
return <div>{user.name}</div>;
});
// Bad: Re-renders when any user property changes
const UserName = observer(({ userName }) => {
return <div>{userName}</div>;
});
Summary
MobX makes state management simple and transparent:
- Observable state automatically tracked
- Actions to modify state
- Computed values cached and derived
- Reactions for side effects
- Less boilerplate than Redux
Key Takeaways:
- Use
makeAutoObservablefor simplicity - Use
runInActionfor async updates - Use
observeron React components - Computed values are cached automatically
- Less code, more productivity
Next Steps:
- Compare with Zustand
- Learn Redux Toolkit
- Try React Context
Resources:
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. MobX is a simple, scalable state management solution that makes state management transparent through reactive programming. It automatically tracks dependencies and updates components.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- Zustand Complete Guide: Simple State Management for React with TypeScript
- Jotai Complete Guide
- Zustand Complete Guide: Simple State Management for React with TypeScript
Keywords Covered in This Article (Related Search Terms)
This article covers MobX, React, State Management, JavaScript, TypeScript, Observable.