乐闻世界logo
搜索文章和话题

How to use MobX in React?

2月22日 14:05

Using MobX in React requires connecting MobX's reactive state with React's rendering mechanism. MobX provides multiple ways to achieve this integration.

Installing Dependencies

bash
npm install mobx mobx-react-lite # or npm install mobx mobx-react

Usage Methods

1. Using observer HOC (mobx-react)

javascript
import React from 'react'; import { observer } from 'mobx-react'; import { makeAutoObservable } from 'mobx'; class TodoStore { todos = []; constructor() { makeAutoObservable(this); } addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); } toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; } } const todoStore = new TodoStore(); // Wrap component with observer const TodoList = observer(() => { return ( <div> <h1>Todo List</h1> <ul> {todoStore.todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => todoStore.toggleTodo(todo.id)} /> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </span> </li> ))} </ul> <button onClick={() => todoStore.addTodo('New Todo')}> Add Todo </button> </div> ); }); export default TodoList;

2. Using useObserver Hook (mobx-react-lite)

javascript
import React from 'react'; import { useObserver } from 'mobx-react-lite'; import { makeAutoObservable } from 'mobx'; class TodoStore { todos = []; constructor() { makeAutoObservable(this); } addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); } toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; } } const todoStore = new TodoStore(); function TodoList() { return useObserver(() => ( <div> <h1>Todo List</h1> <ul> {todoStore.todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => todoStore.toggleTodo(todo.id)} /> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </span> </li> ))} </ul> <button onClick={() => todoStore.addTodo('New Todo')}> Add Todo </button> </div> )); } export default TodoList;

3. Using useLocalObservable Hook (mobx-react-lite)

javascript
import React from 'react'; import { observer, useLocalObservable } from 'mobx-react-lite'; function TodoList() { const store = useLocalObservable(() => ({ todos: [], addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); }, toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; } })); return ( <div> <h1>Todo List</h1> <ul> {store.todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => store.toggleTodo(todo.id)} /> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </span> </li> ))} </ul> <button onClick={() => store.addTodo('New Todo')}> Add Todo </button> </div> ); } export default observer(TodoList);

4. Using React Context to Share Store

javascript
import React, { createContext, useContext } from 'react'; import { observer } from 'mobx-react'; import { makeAutoObservable } from 'mobx'; class TodoStore { todos = []; constructor() { makeAutoObservable(this); } addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); } toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; } } const TodoContext = createContext(null); function TodoProvider({ children }) { const store = new TodoStore(); return ( <TodoContext.Provider value={store}> {children} </TodoContext.Provider> ); } function useTodoStore() { const store = useContext(TodoContext); if (!store) { throw new Error('useTodoStore must be used within TodoProvider'); } return store; } const TodoList = observer(() => { const store = useTodoStore(); return ( <div> <h1>Todo List</h1> <ul> {store.todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => store.toggleTodo(todo.id)} /> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </span> </li> ))} </ul> <button onClick={() => store.addTodo('New Todo')}> Add Todo </button> </div> ); }); function App() { return ( <TodoProvider> <TodoList /> </TodoProvider> ); } export default App;

5. Using Provider and inject (mobx-react legacy)

javascript
import React from 'react'; import { Provider, observer, inject } from 'mobx-react'; import { makeAutoObservable } from 'mobx'; class TodoStore { todos = []; constructor() { makeAutoObservable(this); } addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); } toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; } } const todoStore = new TodoStore(); @inject('todoStore') @observer class TodoList extends React.Component { render() { const { todoStore } = this.props; return ( <div> <h1>Todo List</h1> <ul> {todoStore.todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => todoStore.toggleTodo(todo.id)} /> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </span> </li> ))} </ul> <button onClick={() => todoStore.addTodo('New Todo')}> Add Todo </button> </div> ); } } function App() { return ( <Provider todoStore={todoStore}> <TodoList /> </Provider> ); } export default App;

Best Practices

1. Wrap components that need reactive updates with observer

javascript
// ✅ Correct: only wrap components that need reactive updates const TodoItem = observer(({ todo }) => ( <li> <input type="checkbox" checked={todo.completed} onChange={() => todo.toggle()} /> <span>{todo.text}</span> </li> )); const TodoList = ({ store }) => ( <ul> {store.todos.map(todo => ( <TodoItem key={todo.id} todo={todo} /> ))} </ul> ); // ❌ Wrong: wrap entire app, may cause unnecessary renders const App = observer(() => ( <div> <Header /> <TodoList /> <Footer /> </div> ));

2. Reasonably split Store

javascript
// ✅ Correct: split Store by function class TodoStore { @observable todos = []; @observable filter = 'all'; @action addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); } } class UserStore { @observable user = null; @action setUser(user) { this.user = user; } } class AppStore { todoStore = new TodoStore(); userStore = new UserStore(); } // Share using Context const StoreContext = createContext(new AppStore());

3. Use computed to optimize performance

javascript
class TodoStore { @observable todos = []; @observable filter = 'all'; @computed get filteredTodos() { switch (this.filter) { case 'completed': return this.todos.filter(todo => todo.completed); case 'active': return this.todos.filter(todo => !todo.completed); default: return this.todos; } } } const TodoList = observer(({ store }) => ( <ul> {store.filteredTodos.map(todo => ( <TodoItem key={todo.id} todo={todo} /> ))} </ul> ));

4. Use React.memo to optimize child components

javascript
const TodoItem = observer(React.memo(({ todo }) => ( <li> <input type="checkbox" checked={todo.completed} onChange={() => todo.toggle()} /> <span>{todo.text}</span> </li> )));

5. Use useEffect to cleanup side effects

javascript
import { useEffect } from 'react'; import { reaction } from 'mobx'; const TodoList = observer(({ store }) => { useEffect(() => { const disposer = reaction( () => store.todos.length, (length) => { console.log('Todo count changed:', length); } ); return () => disposer(); }, [store]); return ( <ul> {store.todos.map(todo => ( <TodoItem key={todo.id} todo={todo} /> ))} </ul> ); });

Common Issues

1. Component not updating

javascript
// ❌ Wrong: forgot to use observer function TodoList({ store }) { return ( <ul> {store.todos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul> ); } // ✅ Correct: use observer const TodoList = observer(({ store }) => ( <ul> {store.todos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul> ));

2. Modifying state outside observer component

javascript
// ❌ Wrong: directly modify state outside observer component function App() { const store = useStore(); useEffect(() => { store.todos.push({ id: 1, text: 'Todo' }); // Not in action }, []); return <TodoList store={store} />; } // ✅ Correct: modify state in action function App() { const store = useStore(); useEffect(() => { store.addTodo('Todo'); // In action }, []); return <TodoList store={store} />; }

3. Overusing observer

javascript
// ❌ Wrong: overusing observer const Header = observer(() => <header>Header</header>); const Footer = observer(() => <footer>Footer</footer>); const Main = observer(() => <main>Main</main>); // ✅ Correct: only use observer on components that need reactive updates const Header = () => <header>Header</header>; const Footer = () => <footer>Footer</footer>; const Main = observer(() => <main>Main</main>);

Summary

  • Use observer or useObserver to connect components to MobX
  • Use React Context to share Store
  • Reasonably split Store, organize by function modules
  • Use computed to optimize performance
  • Use React.memo to optimize child components
  • Remember to cleanup reactions in useEffect
  • Avoid overusing observer
  • Always modify state in actions
标签:Mobx