5月30日 01:39
How to use MobX in React?
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
bashnpm install mobx mobx-react-lite # or npm install mobx mobx-react
Usage Methods
1. Using observer HOC (mobx-react)
javascriptimport 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)
javascriptimport 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)
javascriptimport 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
javascriptimport 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)
javascriptimport 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
javascriptclass 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
javascriptconst 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
javascriptimport { 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