MobX is a library for building responsive data models, offering a declarative approach to state management that automatically updates dependent views when data changes.
Creating Observable State
MobX uses the @observable
decorator to create observable objects, arrays, or primitive types. When these change, observers depending on them are automatically updated.
import { observable } from 'mobx';
class Todo {
@observable title;
@observable completed;
constructor(title) {
this.title = title;
this.completed = false;
}
}
const todo1 = new Todo("Learn MobX");
Responsive Computed Values
The @computed
decorator creates computed values based on other observables, automatically updating when dependencies change.
class TodoList {
@observable todos = [];
@computed get completedCount() {
return this.todos.filter(todo => todo.completed).length;
}
}
const todoList = new TodoList();
todoList.todos.push(todo1);
Actions
The @action
decorator ensures state changes occur in a controlled environment, preventing unintended modifications.
class TodoList {
// ...
@action addTodo(title) {
this.todos.push(new Todo(title));
}
@action toggleTodo(index) {
this.todos[index].completed = !this.todos[index].completed;
}
}
Observers
In React, use the mobx-react
library’s observer
higher-order component or useObserver
hook to make components reactive to state changes.
import React from 'react';
import { observer } from 'mobx-react-lite';
const TodoListComponent = observer(({ todoList }) => (
<ul>
{todoList.todos.map((todo, index) => (
<li key={index}>
{todo.title} - {todo.completed ? 'Completed' : 'Not completed'}
</li>
))}
</ul>
));
const App = () => {
const todoList = new TodoList();
return <TodoListComponent todoList={todoList} />;
};
Reactive Programming
MobX’s core is its reactive system, where data changes automatically propagate to dependent computed values and observers without manual setState
calls. Reactive programming emphasizes data flow and change propagation, enabling programs to respond to data changes automatically.
Observables
MobX uses the @observable
decorator or observable
function to create observable values. When these change, any dependent computations or views update automatically.
import { observable } from 'mobx';
class Counter {
@observable count = 0;
}
const counter = new Counter();
// Observer updates automatically
autorun(() => {
console.log(`Current count: ${counter.count}`);
});
// Modify count
counter.count++;
Computed Values
The @computed
decorator creates computed values based on observables, updating automatically when dependencies change.
class Counter {
// ...
@computed get isEven() {
return this.count % 2 === 0;
}
}
// Computed value updates automatically
autorun(() => {
console.log(`Is count even: ${counter.isEven}`);
});
// Modify count
counter.count++;
Reactions
Use autorun
, reaction
, or when
functions to create reactions that execute automatically when data changes. autorun
runs whenever dependencies change, while reaction
runs on specific condition changes.
// Reactive UI update
reaction(
() => counter.count,
(newCount) => {
updateUI(newCount);
},
);
// Conditional reaction
when(
() => counter.count > 10,
() => {
console.log('Count exceeds 10!');
},
);
Actions
The @action
decorator or action
function marks functions that modify state, ensuring changes occur in a controlled environment to prevent unintended side effects.
class Counter {
// ...
@action increment() {
this.count++;
}
}
counter.increment();
Automatic Dependency Tracking
MobX uses proxies and the visitor pattern to automatically track dependencies for computed values and reactions, eliminating manual dependency management.
Dependency Tracking
MobX uses proxies and the visitor pattern to track which computed values and observers depend on observable state, enabling efficient updates.
Proxies
MobX leverages ES6 Proxy objects to create proxies for observable objects. Proxies intercept access and modification operations, allowing MobX to detect when observable state is read or modified.
const observableValue = observable(42);
const proxyValue = new Proxy(observableValue, mobxHandler); // mobxHandler contains interception logic
Visitor Pattern
When accessing observable object properties, MobX records the access path using the visitor pattern, creating a dependency tree that tracks which computed values or reactions depend on which observables.
class ObservableObject {
@observable prop;
// ...
}
const obj = new ObservableObject();
autorun(() => {
console.log(obj.prop);
});
// Accessing obj.prop records the dependency
obj.prop = "new value";
Change Notifications
When an observable state changes, MobX notifies all dependent computed values and reaction functions. Since dependencies are tracked, only affected computations and reactions are triggered.
Minimized Updates
Dependency tracking ensures only necessary computed values and reactions are executed, improving performance by avoiding redundant calculations.
Optimizations
MobX provides optimization mechanisms, such as using asFlat
, asReference
, or asStructure
methods to control how proxies handle changes, further enhancing performance.
Middleware Integration
While MobX is not as tightly integrated with middleware as Redux, you can use mobx-react-devtools
to monitor state changes, offering features like time-travel debugging.
Installing the Plugin
Install mobx-react-devtools
using npm or yarn:
npm install --save mobx-react-devtools
# or
yarn add mobx-react-devtools
Integrating into Your Application
In your main application file (typically index.js
or App.js
), import and include the mobxReactDevTools
component:
import { Provider } from 'mobx-react';
import { mobxReactDevTools } from 'mobx-react-devtools';
// Assuming you have your store
const store = new YourStore();
ReactDOM.render(
<Provider {...store}>
<YourApp />
{/* Add DevTools below your app */}
<mobxReactDevTools />
</Provider>,
document.getElementById('root')
);
Enabling Developer Tools
Once your application is running, a new panel in the browser’s developer tools displays your MobX state and change history. In Chrome or Firefox, find it under the “.mobx-react-devtools” or “Extensions” panel.
Time Travel Debugging
While mobx-react-devtools
does not directly support time-travel debugging, you can use mobx-state-tree
, a MobX-compatible library that provides time-travel functionality by creating a reversible operation history.
TypeScript Support
MobX integrates well with TypeScript, offering type safety and improved code hints.
Type Annotations
In TypeScript, add type annotations to observables, computed values, and actions for type safety.
import { observable, computed, action } from 'mobx';
class Counter {
@observable count: number = 0;
@computed
get isEven(): boolean {
return this.count % 2 === 0;
}
@action
increment(): void {
this.count++;
}
}
Interfaces
Define interfaces for complex observable object structures to ensure data model consistency.
interface Todo {
id: number;
title: string;
completed: boolean;
}
class TodoStore {
@observable todos: Todo[] = [];
// ...
}
Type Inference
TypeScript automatically infers computed value types based on their dependencies.
Type Guards
Use type guard functions to ensure type-safe access to observable objects.
function isTodo(item: any): item is Todo {
return item && typeof item.id === 'number' && typeof item.title === 'string';
}
const todo = getTodoFromSomewhere();
if (isTodo(todo)) {
// TypeScript knows `todo` is of type `Todo`
console.log(todo.title);
}
Type Extensions
Extend ObservableObject
, ObservableArray
, and ObservableMap
types to better describe your data.
makeObservable and makeAutoObservable
In MobX 6, use makeObservable
and makeAutoObservable
for initializing observable state, providing better type safety and automatic type inference.
class TodoStore {
private todos: Todo[] = [];
constructor() {
makeAutoObservable(this);
}
// ...
}
Reactive Data Flow
MobX’s reactive data flow ensures data changes automatically propagate to dependent computations and views, clarifying the relationship between data models and UI.
import React from 'react';
import { render } from 'react-dom';
import { observable, computed, action, reaction } from 'mobx';
import { Provider, observer } from 'mobx-react';
// Create observable object
const counterStore = {
@observable count: 0,
@action increment() {
this.count++;
},
@computed get doubleCount() {
return this.count * 2;
},
};
// Create a reaction to print doubleCount when count changes
reaction(
() => counterStore.count,
(newCount) => {
console.log(`Double count: ${counterStore.doubleCount}`);
},
);
// Create a React component to observe count changes
const Counter = observer(({ store }) => (
<div>
<p>Count: {store.count}</p>
<button onClick={() => store.increment()}>Increment</button>
<p>Double Count: {store.doubleCount}</p>
</div>
));
// Render the application
render(
<Provider counterStore={counterStore}>
<Counter />
</Provider>,
document.getElementById('root'),
);
In this example, counterStore
is an observable object with a count
property. doubleCount
is a computed value that calculates based on count
. When count
increments, both doubleCount
and the Counter
component update automatically without manual setState
calls.
The reaction
function creates an observer that prints doubleCount
when count
changes, forming a clear reactive data flow.
Reactive Functions
Using autorun
, reaction
, or when
functions, you can create functions that execute automatically based on data changes, running until a condition is met or manually stopped.
const disposer = reaction(
() => todoList.completedCount,
(completedCount) => console.log(`There are ${completedCount} completed todos`),
);
// Later, to stop the reaction:
disposer();
Responsive API Calls
For API calls with MobX, wrap asynchronous operations in runInAction
to ensure state updates occur in the correct scope.
async function fetchData() {
await someAsyncCall().then(data => {
runInAction(() => {
// Update state
myStore.setData(data);
});
});
}
Microkernel Architecture
MobX’s core is lightweight, allowing selective inclusion of additional features like mobx-state-tree
or mobx-react-form
for enhanced state management and form handling.
mobx-state-tree
mobx-state-tree
is a MobX-based state management library offering strong type safety, state snapshots, time-travel debugging, and robust error handling.
import { types, onSnapshot } from 'mobx-state-tree';
const Todo = types.model({
title: types.string,
completed: types.boolean,
});
const TodoStore = types.model({
todos: types.array(Todo),
}).actions(self => ({
addTodo(title: string) {
self.todos.push(Todo.create({ title, completed: false }));
},
}));
const store = TodoStore.create({ todos: [] });
onSnapshot(store, (snapshot) => {
console.log('State snapshot:', snapshot);
});
store.addTodo('Learn MobX');
mobx-react-form
mobx-react-form
is a library for creating and managing forms, integrating seamlessly with MobX and providing validation, submission, and reset functionalities.
import React from 'react';
import { Form, Field } from 'mobx-react-form';
import { observable, action } from 'mobx';
import { Provider, observer } from 'mobx-react';
const schema = {
name: 'form',
fields: ['name', 'email'],
labels: { name: 'Name', email: 'Email' },
validators: {
name: 'required',
email: 'required|email',
},
};
class MyForm extends Form {
constructor(values = {}) {
super(schema, values);
this.plugins({
dvr: {},
});
}
}
const form = new MyForm();
@observer
class MyComponent extends React.Component {
@observable submitting = false;
@action submitForm = () => {
this.submitting = true;
if (form.validate()) {
alert('Form submitted successfully!');
form.reset();
} else {
form.focus();
}
this.submitting = false;
};
render() {
return (
<Provider form={form}>
<form onSubmit={this.submitForm}>
<Field type="text" name="name" />
<Field type="email" name="email" />
<button type="submit" disabled={this.submitting}>
Submit
</button>
</form>
</Provider>
);
}
}
render(<MyComponent />, document.getElementById('root'));
These libraries extend MobX’s core functionality, providing advanced abstractions for state management and form handling. This microkernel architecture allows you to choose tools based on project needs, keeping the project lightweight and modular.
Integration with Other Libraries
MobX is not limited to React; it integrates well with Vue.js, Angular, and other libraries. It can also coexist with Redux or other state management libraries for specific use cases.
Hot Reloading and Development Tools
MobX, combined with the mobx-react-devtools
plugin, provides capabilities for inspecting data flow, tracking dependencies, and analyzing performance during development, supporting hot reloading for rapid iteration.
Performance Optimization
MobX’s reactive system automatically tracks dependencies, updating views only when necessary, which is typically more efficient than manual updates. For performance issues, use makeObservable
or makeAutoObservable
with asStructure
or asReference
options, and trackTransitions
to fine-tune performance.
Design Patterns
MobX encourages design patterns like MVC (Model-View-Controller), MVVM (Model-View-ViewModel), or MST (MobX State Tree) to better organize and manage state.
Top comments (0)