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

面试题手册

什么是 MobX,它的工作原理是什么?

MobX 是一个基于信号的、经过实战测试的状态管理库,通过透明地应用函数式响应式编程(FRP)使状态管理变得简单和可扩展。它通过观察者模式自动追踪状态变化,当可观察状态发生变化时,MobX 会自动更新所有依赖该状态的派生值和反应。核心概念1. Observable(可观察状态)使用 observable 或 makeObservable 将普通 JavaScript 对象、数组、Map 等转换为可观察对象。可观察状态的变化会被 MobX 自动追踪。import { observable } from 'mobx';class TodoStore { @observable todos = []; @observable filter = 'all';}2. Action(动作)动作是修改状态的唯一方式,使用 action 装饰器或 runInAction 函数包装状态修改逻辑。这确保了状态变化是可追踪和可预测的。import { action } from 'mobx';class TodoStore { @action addTodo(text) { this.todos.push({ text, completed: false }); }}3. Computed(计算值)计算值是基于可观察状态自动更新的派生值,类似于 Vue 的 computed 属性。只有当依赖的可观察状态发生变化时才会重新计算。import { computed } from 'mobx';class TodoStore { @computed get activeTodos() { return this.todos.filter(todo => !todo.completed); }}4. Reaction(反应)反应是当可观察状态变化时自动执行的副作用,类似于 React 的 useEffect。常用的反应类型包括 autorun、reaction 和 when。import { autorun } from 'mobx';autorun(() => { console.log('当前待办事项数量:', this.todos.length);});工作原理MobX 的工作流程:追踪阶段:当反应或计算值读取可观察状态时,MobX 会建立依赖关系变化阶段:通过 action 修改可观察状态通知阶段:MobX 自动通知所有依赖该状态的反应和计算值更新阶段:反应和计算值自动重新执行或重新计算与 Redux 的区别| 特性 | MobX | Redux ||------|------|-------|| 状态管理 | 自动追踪,无需手动订阅 | 需要手动订阅和 dispatch || 代码量 | 较少,更简洁 | 较多,需要定义 actions、reducers || 学习曲线 | 较平缓 | 较陡峭 || 状态结构 | 可以嵌套 | 推荐扁平化 || 调试工具 | MobX DevTools | Redux DevTools |最佳实践始终使用 action 修改状态:确保状态变化可追踪合理使用 computed:避免重复计算,提高性能避免过度使用 observable:只对需要追踪的状态使用使用 makeAutoObservable:简化装饰器配置分离业务逻辑和 UI:将状态管理逻辑集中在 store 中适用场景MobX 适用于:中大型 React 应用需要复杂状态管理的项目团队希望快速开发的项目状态结构复杂且嵌套的场景不适用于:非常简单的应用需要严格时间旅行调试的场景团队偏好函数式编程范式
阅读 0·2月22日 14:08

MobX 和 Redux 的区别是什么,如何选择?

MobX 和 Redux 都是流行的状态管理库,但它们的设计理念和使用方式有很大的不同。选择哪一个取决于项目需求、团队偏好和具体场景。核心设计理念MobX基于观察者模式:自动追踪状态变化,无需手动订阅命令式编程:直接修改状态,更符合直觉透明响应式:状态变化自动触发更新灵活性高:不强制特定的代码结构Redux基于函数式编程:使用纯函数处理状态变化声明式编程:通过 dispatch action 来修改状态单向数据流:Action → Reducer → Store → View规范性高:强制特定的代码结构代码对比MobX 示例import { observable, action, computed, makeAutoObservable } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeAutoObservable(this); } @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; } } @action addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); } @action toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; } @action setFilter(filter) { this.filter = filter; }}const store = new TodoStore();// 使用store.addTodo('Learn MobX');store.toggleTodo(store.todos[0].id);Redux 示例import { createStore } from 'redux';// Action Typesconst ADD_TODO = 'ADD_TODO';const TOGGLE_TODO = 'TOGGLE_TODO';const SET_FILTER = 'SET_FILTER';// Action Creatorsconst addTodo = (text) => ({ type: ADD_TODO, payload: { id: Date.now(), text, completed: false } });const toggleTodo = (id) => ({ type: TOGGLE_TODO, payload: id });const setFilter = (filter) => ({ type: SET_FILTER, payload: filter });// Reducerconst initialState = { todos: [], filter: 'all'};function todoReducer(state = initialState, action) { switch (action.type) { case ADD_TODO: return { ...state, todos: [...state.todos, action.payload] }; case TOGGLE_TODO: return { ...state, todos: state.todos.map(todo => todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo ) }; case SET_FILTER: return { ...state, filter: action.payload }; default: return state; }}const store = createStore(todoReducer);// Selectorconst selectFilteredTodos = (state) => { switch (state.filter) { case 'completed': return state.todos.filter(todo => todo.completed); case 'active': return state.todos.filter(todo => !todo.completed); default: return state.todos; }};// 使用store.dispatch(addTodo('Learn Redux'));store.dispatch(toggleTodo(store.getState().todos[0].id));详细对比| 特性 | MobX | Redux ||------|------|-------|| 编程范式 | 命令式、面向对象 | 函数式、声明式 || 状态追踪 | 自动追踪 | 手动订阅 || 状态修改 | 直接修改 | 通过 action || 代码量 | 较少 | 较多 || 学习曲线 | 平缓 | 陡峭 || 灵活性 | 高 | 低 || 规范性 | 低 | 高 || 调试工具 | MobX DevTools | Redux DevTools || 时间旅行 | 有限支持 | 完整支持 || 中间件 | 不需要 | 丰富(redux-thunk、redux-saga 等) || 性能 | 自动优化 | 需要手动优化 || 状态结构 | 可以嵌套 | 推荐扁平化 || 类型支持 | 良好 | 需要额外配置 |使用场景适合使用 MobX 的场景快速开发:需要快速原型开发或小型项目复杂状态结构:状态结构复杂且嵌套团队经验:团队更熟悉面向对象编程灵活性优先:需要更多的代码灵活性学习成本:希望降低学习成本// MobX 适合复杂嵌套状态class UserStore { @observable user = { profile: { name: '', email: '', address: { city: '', country: '' } }, preferences: { theme: 'light', language: 'en' } }; @action updateCity(city) { this.user.profile.address.city = city; // 直接修改嵌套属性 }}适合使用 Redux 的场景大型项目:需要严格规范的大型项目团队协作:多人协作,需要统一的代码规范时间旅行:需要完整的时间旅行调试功能中间件需求:需要使用丰富的中间件生态函数式编程:团队偏好函数式编程范式// Redux 适合扁平化状态const initialState = { users: { byId: {}, allIds: [] }, profiles: { byId: {}, allIds: [] }, addresses: { byId: {}, allIds: [] }};// 通过 reducer 处理状态变化function reducer(state = initialState, action) { switch (action.type) { case UPDATE_CITY: return { ...state, addresses: { ...state.addresses, byId: { ...state.addresses.byId, [action.payload.id]: { ...state.addresses.byId[action.payload.id], city: action.payload.city } } } }; default: return state; }}性能对比MobX 性能优势自动优化:自动追踪依赖,只更新必要的组件批量更新:action 内部的状态变化会被批量处理懒计算:computed 值只在被访问时才计算// MobX 自动优化class Store { @observable items = []; @computed get expensiveValue() { console.log('Computing expensive value'); return this.items.reduce((sum, item) => sum + item.value, 0); }}// 只有在访问 expensiveValue 时才会计算console.log(store.expensiveValue); // 计算一次console.log(store.expensiveValue); // 使用缓存,不计算Redux 性能挑战手动优化:需要使用 reselect、memo 等工具优化全量比较:每次 dispatch 都会比较整个状态树需要手动订阅:需要手动选择需要的数据// Redux 需要手动优化import { createSelector } from 'reselect';const selectItems = (state) => state.items;const selectExpensiveValue = createSelector( [selectItems], (items) => { console.log('Computing expensive value'); return items.reduce((sum, item) => sum + item.value, 0); });// 需要手动选择数据const value = selectExpensiveValue(store.getState());调试对比MobX 调试// 使用 MobX DevToolsimport { makeObservable, observable, action } from 'mobx';class Store { @observable count = 0; constructor() { makeObservable(this); } @action increment() { this.count++; }}// 在浏览器中查看状态变化Redux 调试// 使用 Redux DevToolsimport { createStore } from 'redux';const store = createStore( reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());// 可以查看完整的 action 历史、状态变化和时间旅行迁移建议从 MobX 迁移到 Redux重构状态结构:将嵌套状态扁平化创建 action types:定义所有可能的 action编写 reducers:将状态修改逻辑移到 reducers使用中间件:根据需要添加中间件更新组件:使用 useSelector 和 useDispatch从 Redux 迁移到 MobX创建 stores:将 reducer 逻辑转换为 stores使用 observable:将状态转换为 observable添加 actions:将 action creators 转换为 actions更新组件:使用 observer 或 useObserver简化代码:移除不必要的样板代码总结选择 MobX 如果:需要快速开发状态结构复杂且嵌套团队更熟悉面向对象编程希望降低学习成本需要更多的代码灵活性选择 Redux 如果:项目规模较大需要严格的代码规范需要完整的时间旅行调试需要丰富的中间件生态团队偏好函数式编程两者都是优秀的状态管理库,选择哪一个应该基于项目需求和团队情况。在实际项目中,也可以根据不同模块的特点混合使用。
阅读 0·2月22日 14:08

MobX 6 相比 MobX 4/5 有哪些重要变化?

MobX 6 是 MobX 的最新主要版本,相比 MobX 4/5 有许多重要的变化和改进。了解这些变化对于升级和维护项目非常重要。主要变化1. 移除装饰器支持MobX 6 默认不再支持装饰器语法,推荐使用 makeObservable 或 makeAutoObservable。MobX 4/5(装饰器语法):import { observable, action, computed } from 'mobx';class TodoStore { @observable todos = []; @observable filter = 'all'; @computed get completedTodos() { return this.todos.filter(todo => todo.completed); } @action addTodo(text) { this.todos.push({ text, completed: false }); }}MobX 6(推荐方式):import { makeAutoObservable } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeAutoObservable(this); } get completedTodos() { return this.todos.filter(todo => todo.completed); } addTodo(text) { this.todos.push({ text, completed: false }); }}2. makeObservable 和 makeAutoObservableMobX 6 引入了两个新的 API 来替代装饰器:makeObservable需要显式指定每个属性的类型。import { makeObservable, observable, action, computed } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeObservable(this, { todos: observable, filter: observable, completedTodos: computed, addTodo: action }); } get completedTodos() { return this.todos.filter(todo => todo.completed); } addTodo(text) { this.todos.push({ text, completed: false }); }}makeAutoObservable(推荐)自动推断属性类型,更简洁。import { makeAutoObservable } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeAutoObservable(this); } get completedTodos() { return this.todos.filter(todo => todo.completed); } addTodo(text) { this.todos.push({ text, completed: false }); }}3. 移除 configureMobX 6 移除了 configure API,不再需要全局配置。MobX 4/5:import { configure } from 'mobx';configure({ enforceActions: 'always', useProxies: 'ifavailable'});MobX 6:// 不再需要 configure,默认行为已经优化4. 移除 extrasMobX 6 移除了 extras API,相关功能被整合到主 API 中。MobX 4/5:import { extras } from 'mobx';const isObservable = extras.isObservable(obj);MobX 6:import { isObservable } from 'mobx';const isObservable = isObservable(obj);5. 移除 intercept 和 observeMobX 6 移除了 intercept 和 observe API,推荐使用 reaction 替代。MobX 4/5:import { observe } from 'mobx';const disposer = observe(store.todos, (change) => { console.log('Todo changed:', change);});MobX 6:import { reaction } from 'mobx';const disposer = reaction( () => store.todos.length, (length) => { console.log('Todo count changed:', length); });6. 类型推断改进MobX 6 对 TypeScript 的支持更好,类型推断更准确。import { makeAutoObservable } from 'mobx';class TodoStore { todos: Todo[] = []; filter: 'all' | 'completed' | 'active' = 'all'; constructor() { makeAutoObservable<TodoStore>(this); } get completedTodos(): Todo[] { return this.todos.filter(todo => todo.completed); } addTodo(text: string): void { this.todos.push({ text, completed: false }); }}7. 移除 mobx-react 的 inject 和 ProviderMobX 6 推荐使用 React Context API,不再需要 inject 和 Provider。MobX 4/5:import { Provider, inject, observer } from 'mobx-react';@inject('todoStore')@observerclass TodoList extends React.Component { render() { const { todoStore } = this.props; return <div>{/* ... */}</div>; }}function App() { return ( <Provider todoStore={todoStore}> <TodoList /> </Provider> );}MobX 6:import { observer } from 'mobx-react-lite';import { createContext, useContext } from 'react';const TodoContext = createContext(null);function TodoProvider({ children, store }) { 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>{/* ... */}</div>;});function App() { return ( <TodoProvider store={todoStore}> <TodoList /> </TodoProvider> );}8. 性能改进MobX 6 在性能方面有许多改进:更小的包体积:通过 Tree-shaking 减少包体积更快的响应速度:优化了依赖追踪算法更好的内存管理:减少了内存占用9. 错误处理改进MobX 6 提供了更清晰的错误信息。// MobX 6 会提供更清晰的错误信息class Store { data = []; constructor() { makeAutoObservable(this); } // 如果在 action 外修改状态 modifyData() { this.data.push({}); // 警告:在 action 外修改状态 }}迁移指南从 MobX 4/5 迁移到 MobX 61. 移除装饰器之前:class Store { @observable data = []; @action addData(item) { this.data.push(item); }}之后:class Store { data = []; constructor() { makeAutoObservable(this); } addData(item) { this.data.push(item); }}2. 更新 mobx-react之前:import { Provider, inject, observer } from 'mobx-react';@inject('store')@observerclass Component extends React.Component { render() { const { store } = this.props; return <div>{store.data}</div>; }}之后:import { observer } from 'mobx-react-lite';const Component = observer(() => { const store = useStore(); return <div>{store.data}</div>;});3. 移除 configure之前:import { configure } from 'mobx';configure({ enforceActions: 'always'});之后:// 不再需要 configure4. 更新 extras之前:import { extras } from 'mobx';if (extras.isObservable(obj)) { // ...}之后:import { isObservable } from 'mobx';if (isObservable(obj)) { // ...}最佳实践1. 使用 makeAutoObservableclass Store { data = []; constructor() { makeAutoObservable(this); }}2. 使用 mobx-react-liteimport { observer } from 'mobx-react-lite';const Component = observer(() => { return <div>{/* ... */}</div>;});3. 使用 React Contextconst StoreContext = createContext(null);function StoreProvider({ children, store }) { return ( <StoreContext.Provider value={store}> {children} </StoreContext.Provider> );}function useStore() { const store = useContext(StoreContext); if (!store) { throw new Error('useStore must be used within StoreProvider'); } return store;}4. 使用 TypeScriptclass Store { data: Data[] = []; constructor() { makeAutoObservable<Store>(this); }}常见问题1. 如何继续使用装饰器?如果需要继续使用装饰器,可以安装 mobx-undecorate 包。import { decorate, observable, action } from 'mobx';class Store { data = []; addData(item) { this.data.push(item); }}decorate(Store, { data: observable, addData: action});2. 如何处理类型推断?使用 makeAutoObservable 时,可以传入泛型参数。class Store { data: Data[] = []; constructor() { makeAutoObservable<Store>(this); }}3. 如何处理 action.bound?使用 action.bound 时,需要在 makeObservable 中指定。class Store { data = []; constructor() { makeObservable(this, { data: observable, addData: action.bound }); } addData(item) { this.data.push(item); }}总结MobX 6 移除了装饰器支持,推荐使用 makeAutoObservable移除了 configure、extras、intercept、observe 等 API推荐使用 mobx-react-lite 和 React Context对 TypeScript 的支持更好性能和错误处理都有改进迁移相对简单,主要是替换装饰器语法
阅读 0·2月22日 14:08

MobX 中 action 的作用和使用方法是什么?

在 MobX 中,action 是修改 observable 状态的唯一推荐方式。使用 action 可以确保状态变化是可追踪、可预测的,并且能够优化性能。Action 的作用批量更新:action 内部的所有状态变化会被批量处理,减少不必要的重新计算可追踪性:所有状态变化都集中在 action 中,便于调试和追踪事务性:action 内部的状态变化是原子的,要么全部成功,要么全部失败性能优化:减少 reaction 的触发次数,提高性能Action 的使用方式1. 使用装饰器import { observable, action } from 'mobx';class TodoStore { @observable todos = []; @action addTodo(text) { this.todos.push({ text, completed: false }); } @action.bound removeTodo(id) { this.todos = this.todos.filter(todo => todo.id !== id); } @action async fetchTodos() { const response = await fetch('/api/todos'); const data = await response.json(); this.todos = data; }}2. 使用 makeObservableimport { makeObservable, observable, action } from 'mobx';class TodoStore { todos = []; constructor() { makeObservable(this, { todos: observable, addTodo: action, removeTodo: action.bound, fetchTodos: action }); } addTodo(text) { this.todos.push({ text, completed: false }); } removeTodo(id) { this.todos = this.todos.filter(todo => todo.id !== id); } async fetchTodos() { const response = await fetch('/api/todos'); const data = await response.json(); this.todos = data; }}3. 使用 runInActionimport { observable, runInAction } from 'mobx';class TodoStore { todos = []; async fetchTodos() { const response = await fetch('/api/todos'); const data = await response.json(); runInAction(() => { this.todos = data; }); }}Action 的类型1. action最基础的 action 装饰器,用于包装状态修改方法。@actionupdateTodo(id, updates) { const todo = this.todos.find(t => t.id === id); if (todo) { Object.assign(todo, updates); }}2. action.bound自动绑定 this 的 action,避免在回调函数中丢失 this 上下文。@action.boundhandleClick() { this.count++;}// 使用时不需要 bind<button onClick={this.handleClick}>Click</button>3. runInAction在异步操作中修改状态时使用。async loadData() { const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; });}Action 的最佳实践1. 始终在 action 中修改状态// ❌ 错误:直接修改状态class Store { @observable count = 0; increment() { this.count++; // 不是 action }}// ✅ 正确:在 action 中修改状态class Store { @observable count = 0; @action increment() { this.count++; }}2. 使用 action.bound 处理事件处理器class Component { @observable count = 0; @action.bound handleClick() { this.count++; } render() { return <button onClick={this.handleClick}>Click</button>; }}3. 异步操作中使用 runInAction@actionasync fetchUser(id) { this.loading = true; try { const response = await fetch(`/api/users/${id}`); const data = await response.json(); runInAction(() => { this.user = data; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); }}4. 合理拆分 action// ❌ 过于复杂的 action@actionhandleComplexOperation(data) { this.loading = true; this.data = data; this.filtered = data.filter(item => item.active); this.count = this.filtered.length; this.timestamp = Date.now(); this.loading = false;}// ✅ 拆分为多个小 action@actionhandleComplexOperation(data) { this.setLoading(true); this.setData(data); this.processData(data); this.setLoading(false);}@actionsetLoading(loading) { this.loading = loading;}@actionsetData(data) { this.data = data;}@actionprocessData(data) { this.filtered = data.filter(item => item.active); this.count = this.filtered.length; this.timestamp = Date.now();}常见错误1. 在异步回调中直接修改状态// ❌ 错误@actionasync fetchData() { const data = await fetch('/api/data'); this.data = await data.json(); // 不在 action 中}// ✅ 正确@actionasync fetchData() { const data = await fetch('/api/data'); runInAction(() => { this.data = data.json(); });}2. 忘记使用 action.bound// ❌ 错误:this 可能丢失class Component { @action handleClick() { this.count++; } render() { return <button onClick={this.handleClick}>Click</button>; }}// ✅ 正确:使用 action.boundclass Component { @action.bound handleClick() { this.count++; } render() { return <button onClick={this.handleClick}>Click</button>; }}性能优化使用 action 批量更新:减少 reaction 触发次数避免在循环中创建 action:复用 action 函数合理使用 runInAction:只在必要时使用使用 action.bound:避免重复 bind总结action 是修改 observable 状态的唯一推荐方式使用 action 可以批量更新、提高性能、便于调试优先使用 action.bound 处理事件处理器异步操作中使用 runInAction 修改状态合理拆分复杂的 action,提高可维护性
阅读 0·2月22日 14:06

MobX 中 computed 的作用和使用场景有哪些?

在 MobX 中,computed 是基于 observable 状态自动更新的派生值,类似于 Vue 的 computed 属性。computed 值会缓存计算结果,只有当依赖的 observable 状态发生变化时才会重新计算。Computed 的特性自动缓存:计算结果会被缓存,避免重复计算懒计算:只有在被访问时才会计算自动追踪依赖:自动追踪依赖的 observable 状态自动更新:依赖的状态变化时自动重新计算纯函数:computed 应该是纯函数,不应该有副作用Computed 的使用方式1. 使用装饰器import { observable, computed } from 'mobx';class TodoStore { @observable todos = []; @observable filter = 'all'; @computed get completedTodos() { return this.todos.filter(todo => todo.completed); } @computed get activeTodos() { return this.todos.filter(todo => !todo.completed); } @computed get filteredTodos() { switch (this.filter) { case 'completed': return this.completedTodos; case 'active': return this.activeTodos; default: return this.todos; } }}2. 使用 makeObservableimport { makeObservable, observable, computed } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeObservable(this, { todos: observable, filter: observable, completedTodos: computed, activeTodos: computed, filteredTodos: computed }); } get completedTodos() { return this.todos.filter(todo => todo.completed); } get activeTodos() { return this.todos.filter(todo => !todo.completed); } get filteredTodos() { switch (this.filter) { case 'completed': return this.completedTodos; case 'active': return this.activeTodos; default: return this.todos; } }}3. 使用 makeAutoObservable(推荐)import { makeAutoObservable } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeAutoObservable(this); } get completedTodos() { return this.todos.filter(todo => todo.completed); } get activeTodos() { return this.todos.filter(todo => !todo.completed); } get filteredTodos() { switch (this.filter) { case 'completed': return this.completedTodos; case 'active': return this.activeTodos; default: return this.todos; } }}4. 使用 computed 函数import { observable, computed } from 'mobx';const store = observable({ todos: [], filter: 'all'});const completedTodos = computed(() => store.todos.filter(todo => todo.completed));const activeTodos = computed(() => store.todos.filter(todo => !todo.completed));console.log(completedTodos.get()); // 获取计算值Computed 的配置选项1. name为 computed 设置名称,便于调试。@computed({ name: 'completedTodos' })get completedTodos() { return this.todos.filter(todo => todo.completed);}2. equals自定义比较函数,决定是否需要重新计算。@computed({ equals: (a, b) => a.length === b.length && a.every(item => b.includes(item))})get filteredTodos() { return this.todos.filter(todo => todo.completed);}3. keepAlive保持计算值活跃,即使没有被访问。@computed({ keepAlive: true })get expensiveComputation() { return this.largeArray.reduce((sum, item) => sum + item.value, 0);}Computed 的最佳实践1. 使用 computed 缓存复杂计算class Store { @observable items = []; @computed get totalValue() { return this.items.reduce((sum, item) => sum + item.value, 0); } @computed get averageValue() { return this.items.length > 0 ? this.totalValue / this.items.length : 0; }}2. 避免在 computed 中产生副作用// ❌ 错误:computed 中有副作用@computed get filteredItems() { console.log('Filtering items'); // 副作用 return this.items.filter(item => item.active);}// ✅ 正确:使用 reaction 处理副作用@computed get filteredItems() { return this.items.filter(item => item.active);}constructor() { reaction( () => this.filteredItems, (items) => console.log('Filtered items:', items) );}3. 合理使用 computed 链class Store { @observable products = []; @observable category = 'all'; @observable minPrice = 0; @observable maxPrice = Infinity; @computed get filteredByCategory() { return this.category === 'all' ? this.products : this.products.filter(p => p.category === this.category); } @computed get filteredByPrice() { return this.filteredByCategory.filter( p => p.price >= this.minPrice && p.price <= this.maxPrice ); } @computed get sortedProducts() { return [...this.filteredByPrice].sort((a, b) => a.price - b.price); }}4. 使用 computed 处理表单验证class FormStore { @observable username = ''; @observable email = ''; @observable password = ''; @computed get usernameError() { if (this.username.length < 3) { return 'Username must be at least 3 characters'; } return ''; } @computed get emailError() { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(this.email)) { return 'Invalid email format'; } return ''; } @computed get passwordError() { if (this.password.length < 8) { return 'Password must be at least 8 characters'; } return ''; } @computed get isValid() { return !this.usernameError && !this.emailError && !this.passwordError; }}Computed vs Reaction| 特性 | Computed | Reaction ||------|----------|----------|| 用途 | 计算派生值 | 执行副作用 || 返回值 | 返回计算结果 | 不返回值 || 缓存 | 自动缓存 | 不缓存 || 触发时机 | 被访问时 | 依赖变化时立即执行 || 适用场景 | 数据转换、过滤、聚合 | 日志、API 调用、DOM 操作 |性能优化合理使用 computed:避免过度使用,只在需要缓存时使用避免复杂计算:如果计算非常耗时,考虑使用 memoization使用 computed 链:将复杂计算拆分为多个小的 computed避免在 computed 中创建新对象:可能导致不必要的重新计算常见错误1. 在 computed 中修改状态// ❌ 错误:computed 中修改状态@computed get filteredItems() { this.lastFilterTime = Date.now(); // 修改状态 return this.items.filter(item => item.active);}// ✅ 正确:使用 reaction 处理副作用@computed get filteredItems() { return this.items.filter(item => item.active);}constructor() { reaction( () => this.filteredItems, () => { this.lastFilterTime = Date.now(); } );}2. 在 computed 中使用异步操作// ❌ 错误:computed 中使用异步操作@computed async get userData() { const response = await fetch('/api/user'); return response.json();}// ✅ 正确:使用 reaction 处理异步操作constructor() { reaction( () => this.userId, async (id) => { const response = await fetch(`/api/user/${id}`); runInAction(() => { this.userData = await response.json(); }); } );}总结computed 是基于 observable 状态自动更新的派生值computed 会自动缓存计算结果,提高性能computed 应该是纯函数,不应该有副作用合理使用 computed 链处理复杂计算避免在 computed 中修改状态或使用异步操作使用 reaction 处理副作用和异步操作
阅读 0·2月22日 14:05

MobX 中 observable 的使用方法和注意事项有哪些?

在 MobX 中,observable 是核心概念之一,它将普通的 JavaScript 对象、数组、Map、Set 等数据结构转换为可观察对象,使 MobX 能够追踪其变化。Observable 的使用方式1. 使用装饰器(Decorator)import { observable, action, computed } from 'mobx';class TodoStore { @observable todos = []; @observable filter = 'all'; @computed get completedTodos() { return this.todos.filter(todo => todo.completed); } @action addTodo(text) { this.todos.push({ text, completed: false }); }}2. 使用 makeObservableimport { makeObservable, observable, action, computed } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeObservable(this, { todos: observable, filter: observable, completedTodos: computed, addTodo: action }); } get completedTodos() { return this.todos.filter(todo => todo.completed); } addTodo(text) { this.todos.push({ text, completed: false }); }}3. 使用 makeAutoObservable(推荐)import { makeAutoObservable } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeAutoObservable(this); } get completedTodos() { return this.todos.filter(todo => todo.completed); } addTodo(text) { this.todos.push({ text, completed: false }); }}4. 使用 observable 函数import { observable } from 'mobx';const state = observable({ todos: [], filter: 'all'});const completedTodos = observable(() => state.todos.filter(todo => todo.completed));Observable 的特性1. 自动追踪依赖当 observable 对象被读取时,MobX 会自动建立依赖关系。import { observable, autorun } from 'mobx';const store = observable({ count: 0});autorun(() => { console.log('Count is:', store.count);});store.count = 1; // 自动触发 autorun2. 深度可观察observable 默认是深度可观察的,会递归地将对象的所有属性都转换为可观察。const store = observable({ user: { name: 'John', address: { city: 'New York' } }});store.user.address.city = 'Boston'; // 会被追踪3. 数组和 Map 的特殊处理observable 数组和 Map 提供了额外的 API,但保持与原生 API 兼容。const list = observable([1, 2, 3]);list.push(4); // MobX 会追踪这个变化list.splice(0, 1); // 也会被追踪Observable 配置选项1. shallow(浅层可观察)只追踪对象本身的变化,不追踪嵌套对象。import { observable } from 'mobx';const store = observable({ user: { name: 'John' }}, {}, { deep: false });store.user = { name: 'Jane' }; // 会被追踪store.user.name = 'Bob'; // 不会被追踪2. asMap、asArray显式指定数据结构类型。const map = observable.map({ key: 'value' });const array = observable.array([1, 2, 3]);注意事项不要在 action 外部修改 observable 状态避免在 constructor 中使用装饰器合理使用 computed 缓存计算结果对于大型对象,考虑使用 shallow 优化性能observable 对象不能被冻结(Object.freeze)最佳实践优先使用 makeAutoObservable 简化配置对于简单状态,使用 observable 函数对于复杂状态管理,使用类 + 装饰器或 makeObservable合理使用 computed 避免重复计算始终在 action 中修改 observable 状态
阅读 0·2月22日 14:05

MobX 中 reaction 的类型和使用场景是什么?

在 MobX 中,reaction 是用于处理副作用的机制,当 observable 状态发生变化时自动执行指定的函数。reaction 类似于 React 的 useEffect,但更加灵活和高效。Reaction 的类型1. autorun自动追踪依赖并在依赖变化时立即执行,适合需要立即执行的场景。import { observable, autorun } from 'mobx';class TodoStore { @observable todos = []; constructor() { autorun(() => { console.log('Total todos:', this.todos.length); // 保存到 localStorage localStorage.setItem('todos', JSON.stringify(this.todos)); }); }}2. reaction提供更细粒度的控制,可以指定追踪的数据和执行函数,适合需要控制执行时机的场景。import { observable, reaction } from 'mobx';class TodoStore { @observable todos = []; @observable filter = 'all'; constructor() { reaction( () => this.todos.length, // 追踪的数据 (length) => { console.log('Todo count changed:', length); }, { fireImmediately: false } // 配置选项 ); }}3. when当条件满足时执行一次,然后自动清理,适合一次性操作。import { observable, when } from 'mobx';class TodoStore { @observable todos = []; @observable loading = false; constructor() { when( () => !this.loading && this.todos.length === 0, () => { console.log('Ready to load todos'); this.loadTodos(); } ); } @action loadTodos() { this.loading = true; // 加载数据... }}Reaction 的配置选项1. fireImmediately是否立即执行一次。reaction( () => this.filter, (filter) => { console.log('Current filter:', filter); }, { fireImmediately: true } // 立即执行一次);2. delay延迟执行,防抖效果。reaction( () => this.searchQuery, (query) => { this.performSearch(query); }, { delay: 300 } // 延迟 300ms 执行);3. equals自定义比较函数,决定是否触发 reaction。reaction( () => this.items, (items) => { console.log('Items changed'); }, { equals: (a, b) => { return a.length === b.length && a.every(item => b.includes(item)); } });4. name为 reaction 设置名称,便于调试。reaction( () => this.todos, (todos) => { console.log('Todos updated'); }, { name: 'saveTodosToLocalStorage' });Reaction 的使用场景1. 数据持久化class TodoStore { @observable todos = []; constructor() { // 从 localStorage 加载 this.todos = JSON.parse(localStorage.getItem('todos') || '[]'); // 保存到 localStorage autorun(() => { localStorage.setItem('todos', JSON.stringify(this.todos)); }); }}2. 日志记录class Store { @observable user = null; @observable actions = []; constructor() { reaction( () => this.user, (user) => { console.log('User changed:', user); this.actions.push({ type: 'USER_CHANGE', user, timestamp: Date.now() }); } ); }}3. API 调用class ProductStore { @observable categoryId = null; @observable products = []; @observable loading = false; constructor() { reaction( () => this.categoryId, async (categoryId) => { if (categoryId) { this.loading = true; try { const response = await fetch(`/api/products?category=${categoryId}`); const data = await response.json(); runInAction(() => { this.products = data; this.loading = false; }); } catch (error) { runInAction(() => { this.loading = false; }); } } } ); }}4. 搜索防抖class SearchStore { @observable query = ''; @observable results = []; @observable loading = false; constructor() { reaction( () => this.query, async (query) => { if (query.length > 2) { this.loading = true; try { const response = await fetch(`/api/search?q=${query}`); const data = await response.json(); runInAction(() => { this.results = data; this.loading = false; }); } catch (error) { runInAction(() => { this.loading = false; }); } } }, { delay: 300 } // 防抖 300ms ); }}5. 条件初始化class AppStore { @observable initialized = false; @observable user = null; constructor() { when( () => this.initialized, () => { this.loadUserData(); } ); } @action loadUserData() { // 加载用户数据 }}Reaction 的清理1. 使用 dispose 清理class Component { disposer = null; componentDidMount() { this.disposer = reaction( () => this.store.todos, (todos) => { console.log('Todos changed:', todos); } ); } componentWillUnmount() { if (this.disposer) { this.disposer(); // 清理 reaction } }}2. 使用 useEffect 清理import { useEffect } from 'react';import { reaction } from 'mobx';function TodoList({ store }) { useEffect(() => { const disposer = reaction( () => store.todos, (todos) => { console.log('Todos changed:', todos); } ); return () => disposer(); // 清理 reaction }, [store]); return <div>{/* ... */}</div>;}Reaction vs Computed| 特性 | Reaction | Computed ||------|----------|----------|| 用途 | 执行副作用 | 计算派生值 || 返回值 | 不返回值 | 返回计算结果 || 缓存 | 不缓存 | 自动缓存 || 触发时机 | 依赖变化时立即执行 | 被访问时才计算 || 适用场景 | 日志、API 调用、DOM 操作 | 数据转换、过滤、聚合 |最佳实践1. 合理选择 reaction 类型// ✅ 使用 autorun:需要立即执行autorun(() => { console.log('Current state:', this.state);});// ✅ 使用 reaction:需要控制执行时机reaction( () => this.userId, (id) => this.loadUser(id));// ✅ 使用 when:一次性操作when( () => this.ready, () => this.start());2. 避免在 reaction 中修改依赖的状态// ❌ 错误:在 reaction 中修改依赖的状态reaction( () => this.count, (count) => { this.count = count + 1; // 会导致无限循环 });// ✅ 正确:使用 action 修改其他状态reaction( () => this.count, (count) => { this.setCount(count + 1); });3. 使用 delay 防抖// ✅ 使用 delay 防抖,避免频繁触发reaction( () => this.searchQuery, (query) => this.performSearch(query), { delay: 300 });4. 记得清理 reaction// ✅ 在组件卸载时清理 reactionuseEffect(() => { const disposer = reaction( () => store.data, (data) => console.log(data) ); return () => disposer();}, [store]);常见错误1. 忘记清理 reaction// ❌ 错误:忘记清理 reactionclass Component { componentDidMount() { reaction(() => this.store.data, (data) => { console.log(data); }); }}// ✅ 正确:清理 reactionclass Component { disposer = null; componentDidMount() { this.disposer = reaction(() => this.store.data, (data) => { console.log(data); }); } componentWillUnmount() { if (this.disposer) { this.disposer(); } }}2. 在 reaction 中直接修改状态// ❌ 错误:在 reaction 中直接修改状态reaction( () => this.count, (count) => { this.count = count + 1; // 不在 action 中 });// ✅ 正确:在 action 中修改状态reaction( () => this.count, (count) => { runInAction(() => { this.count = count + 1; }); });总结reaction 是 MobX 中处理副作用的机制autorun 适合需要立即执行的场景reaction 提供更细粒度的控制when 适合一次性操作使用 delay 可以实现防抖效果记得在组件卸载时清理 reaction避免在 reaction 中修改依赖的状态使用 action 或 runInAction 修改状态
阅读 0·2月22日 14:05

MobX 中如何处理异步操作?

在 MobX 中,异步操作需要特别注意,因为状态变化必须在 action 中进行。MobX 提供了多种方式来处理异步操作。处理异步操作的方式1. 使用 runInActionimport { observable, action, runInAction } from 'mobx';class UserStore { @observable users = []; @observable loading = false; @observable error = null; @action async fetchUsers() { this.loading = true; this.error = null; try { const response = await fetch('/api/users'); const data = await response.json(); runInAction(() => { this.users = data; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); } }}2. 使用 async actionimport { observable, action } from 'mobx';class UserStore { @observable users = []; @observable loading = false; @observable error = null; @action.bound async fetchUsers() { this.loading = true; this.error = null; try { const response = await fetch('/api/users'); const data = await response.json(); this.users = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; } }}3. 使用 flowimport { observable, flow } from 'mobx';class UserStore { @observable users = []; @observable loading = false; @observable error = null; fetchUsers = flow(function* () { this.loading = true; this.error = null; try { const response = yield fetch('/api/users'); const data = yield response.json(); this.users = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; } });}详细对比runInAction优点:灵活性高,可以在任何地方使用适合处理复杂的异步逻辑可以精确控制状态更新的时机缺点:需要手动包裹状态更新代码代码结构可能不够清晰适用场景:需要在异步操作的不同阶段更新状态复杂的异步逻辑需要精确控制状态更新时机@actionasync complexOperation() { this.loading = true; try { const data1 = await this.fetchData1(); runInAction(() => { this.data1 = data1; }); const data2 = await this.fetchData2(data1.id); runInAction(() => { this.data2 = data2; }); const result = await this.processData(data1, data2); runInAction(() => { this.result = result; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); }}async action优点:代码结构清晰自动处理状态更新不需要手动包裹代码缺点:灵活性较低所有状态更新都在同一个 action 中适用场景:简单的异步操作不需要精确控制状态更新时机代码结构优先的场景@action.boundasync simpleFetch() { this.loading = true; try { const response = await fetch('/api/data'); const data = await response.json(); this.data = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; }}flow优点:代码结构最清晰自动处理状态更新支持取消操作更好的错误处理缺点:需要学习 generator 语法兼容性问题(需要 polyfill)适用场景:复杂的异步流程需要取消操作的场景需要更好的错误处理fetchUsers = flow(function* () { this.loading = true; this.error = null; try { const response = yield fetch('/api/users'); const data = yield response.json(); this.users = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; }});// 可以取消 flowconst fetchTask = store.fetchUsers();fetchTask.cancel();最佳实践1. 统一使用 async actionclass ApiStore { @observable data = null; @observable loading = false; @observable error = null; @action.bound async fetchData(url) { this.loading = true; this.error = null; try { const response = await fetch(url); const data = await response.json(); this.data = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; } }}2. 使用 flow 处理复杂流程class OrderStore { @observable orders = []; @observable loading = false; @observable error = null; createOrder = flow(function* (orderData) { this.loading = true; this.error = null; try { // 验证订单 const validated = yield this.validateOrder(orderData); // 创建订单 const order = yield this.createOrderApi(validated); // 支付订单 const paid = yield this.payOrder(order.id); // 更新状态 this.orders.push(paid); this.loading = false; return paid; } catch (error) { this.error = error.message; this.loading = false; throw error; } });}3. 使用 runInAction 处理分步更新class UploadStore { @observable progress = 0; @observable files = []; @observable uploading = false; @action async uploadFiles(files) { this.uploading = true; this.progress = 0; try { for (let i = 0; i < files.length; i++) { const file = files[i]; await this.uploadFile(file); runInAction(() => { this.files.push(file); this.progress = ((i + 1) / files.length) * 100; }); } runInAction(() => { this.uploading = false; }); } catch (error) { runInAction(() => { this.uploading = false; }); throw error; } }}4. 使用 reaction 处理自动加载class DataStore { @observable userId = null; @observable userData = null; @observable loading = false; constructor() { reaction( () => this.userId, (userId) => { if (userId) { this.loadUserData(userId); } } ); } @action.bound async loadUserData(userId) { this.loading = true; try { const response = await fetch(`/api/users/${userId}`); const data = await response.json(); this.userData = data; this.loading = false; } catch (error) { this.loading = false; } }}5. 错误处理和重试class Store { @observable data = null; @observable loading = false; @observable error = null; @observable retryCount = 0; @action.bound async fetchDataWithRetry(url, maxRetries = 3) { this.loading = true; this.error = null; for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; this.retryCount = 0; }); return data; } catch (error) { runInAction(() => { this.retryCount = i + 1; }); if (i === maxRetries - 1) { runInAction(() => { this.error = error.message; this.loading = false; }); throw error; } // 等待后重试 await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } } }}常见错误1. 在 async 函数中直接修改状态// ❌ 错误:在 async 函数中直接修改状态@actionasync fetchData() { this.loading = true; const response = await fetch('/api/data'); const data = await response.json(); this.data = data; // 不在 action 中 this.loading = false;}// ✅ 正确:使用 runInAction 或 async action@actionasync fetchData() { this.loading = true; const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; });}// 或者@action.boundasync fetchData() { this.loading = true; const response = await fetch('/api/data'); const data = await response.json(); this.data = data; this.loading = false;}2. 忘记处理错误// ❌ 错误:忘记处理错误@actionasync fetchData() { this.loading = true; const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; });}// ✅ 正确:处理错误@actionasync fetchData() { this.loading = true; this.error = null; try { const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); }}3. 忘记重置 loading 状态// ❌ 错误:忘记重置 loading 状态@actionasync fetchData() { this.loading = true; try { const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; }); } catch (error) { runInAction(() => { this.error = error.message; }); }}// ✅ 正确:在所有分支中重置 loading 状态@actionasync fetchData() { this.loading = true; try { const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); }}性能优化1. 使用 debounce 防抖import { debounce } from 'lodash';class SearchStore { @observable query = ''; @observable results = []; @observable loading = false; constructor() { this.debouncedSearch = debounce(this.performSearch.bind(this), 300); } @action setQuery(query) { this.query = query; this.debouncedSearch(); } @action.bound async performSearch() { if (this.query.length < 2) { this.results = []; return; } this.loading = true; try { const response = await fetch(`/api/search?q=${this.query}`); const data = await response.json(); this.results = data; this.loading = false; } catch (error) { this.loading = false; } }}2. 使用 requestAnimationFrame 优化 UI 更新@actionasync loadData() { this.loading = true; const data = await this.fetchData(); // 使用 requestAnimationFrame 优化 UI 更新 requestAnimationFrame(() => { runInAction(() => { this.data = data; this.loading = false; }); });}总结使用 async action 处理简单的异步操作使用 runInAction 处理需要精确控制状态更新时机的场景使用 flow 处理复杂的异步流程始终处理错误和重置 loading 状态使用 reaction 处理自动加载使用 debounce 防抖优化性能使用 requestAnimationFrame 优化 UI 更新
阅读 0·2月22日 14:05

如何在 React 中使用 MobX?

在 React 中使用 MobX 需要将 MobX 的响应式状态与 React 的渲染机制连接起来。MobX 提供了多种方式来实现这种集成。安装依赖npm install mobx mobx-react-lite# 或者npm install mobx mobx-react使用方式1. 使用 observer 高阶组件(mobx-react)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();// 使用 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. 使用 useObserver Hook(mobx-react-lite)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. 使用 useLocalObservable Hook(mobx-react-lite)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. 使用 React Context 共享 Storeimport 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. 使用 Provider 和 inject(mobx-react 旧版)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')@observerclass 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;最佳实践1. 使用 observer 包裹需要响应式更新的组件// ✅ 正确:只包裹需要响应式更新的组件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>);// ❌ 错误:包裹整个应用,可能导致不必要的渲染const App = observer(() => ( <div> <Header /> <TodoList /> <Footer /> </div>));2. 合理拆分 Store// ✅ 正确:按功能拆分 Storeclass 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();}// 使用 Context 共享const StoreContext = createContext(new AppStore());3. 使用 computed 优化性能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. 使用 React.memo 优化子组件const TodoItem = observer(React.memo(({ todo }) => ( <li> <input type="checkbox" checked={todo.completed} onChange={() => todo.toggle()} /> <span>{todo.text}</span> </li>)));5. 使用 useEffect 清理副作用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> );});常见问题1. 组件不更新// ❌ 错误:忘记使用 observerfunction TodoList({ store }) { return ( <ul> {store.todos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul> );}// ✅ 正确:使用 observerconst TodoList = observer(({ store }) => ( <ul> {store.todos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul>));2. 在 observer 组件外修改状态// ❌ 错误:在 observer 组件外直接修改状态function App() { const store = useStore(); useEffect(() => { store.todos.push({ id: 1, text: 'Todo' }); // 不在 action 中 }, []); return <TodoList store={store} />;}// ✅ 正确:在 action 中修改状态function App() { const store = useStore(); useEffect(() => { store.addTodo('Todo'); // 在 action 中 }, []); return <TodoList store={store} />;}3. 过度使用 observer// ❌ 错误:过度使用 observerconst Header = observer(() => <header>Header</header>);const Footer = observer(() => <footer>Footer</footer>);const Main = observer(() => <main>Main</main>);// ✅ 正确:只在需要响应式更新的组件使用 observerconst Header = () => <header>Header</header>;const Footer = () => <footer>Footer</footer>;const Main = observer(() => <main>Main</main>);总结使用 observer 或 useObserver 将组件连接到 MobX使用 React Context 共享 Store合理拆分 Store,按功能模块组织使用 computed 优化性能使用 React.memo 优化子组件记得在 useEffect 中清理 reaction避免过度使用 observer始终在 action 中修改状态
阅读 0·2月22日 14:05

MobX 6 有哪些主要变化和新特性?

MobX 6 是 MobX 的一个重大版本更新,引入了许多重要的变化和新特性。以下是 MobX 6 的主要变化:1. 强制使用 ActionMobX 5 及之前class Store { @observable count = 0; increment() { this.count++; // 可以直接修改 }}MobX 6class Store { @observable count = 0; @action // 必须使用 action increment() { this.count++; }}在 MobX 6 中,所有状态修改都必须在 action 中进行。这是为了提供更好的可预测性和调试体验。2. 装饰器 API 的变化MobX 5import { observable, computed, action } from 'mobx';class Store { @observable count = 0; @computed get doubled() { return this.count * 2; } @action increment() { this.count++; }}MobX 6import { makeObservable, observable, computed, action } from 'mobx';class Store { count = 0; constructor() { makeObservable(this); } get doubled() { return this.count * 2; } increment() { this.count++; }}MobX 6 推荐使用 makeObservable 而不是装饰器,但装饰器仍然支持。3. makeAutoObservable 的引入MobX 6 引入了 makeAutoObservable,它可以自动推断属性的类型:import { makeAutoObservable } from 'mobx';class Store { count = 0; firstName = 'John'; lastName = 'Doe'; constructor() { makeAutoObservable(this); } get fullName() { return `${this.firstName} ${this.lastName}`; } increment() { this.count++; }}makeAutoObservable 会自动:将 getter 标记为 computed将方法标记为 action将字段标记为 observable4. 配置 API 的简化MobX 5import { configure } from 'mobx';configure({ enforceActions: 'always', useProxies: 'always', computedRequiresReaction: true});MobX 6import { configure } from 'mobx';configure({ enforceActions: 'always', // 默认值 useProxies: 'ifavailable', // 默认值 computedRequiresReaction: false // 默认值});MobX 6 的默认配置更加严格和合理。5. Proxy 的强制使用MobX 6 强制使用 Proxy(在支持的浏览器中),这提供了更好的性能和更简单的 API。Proxy 的优势更好的性能更简单的 API更好的 TypeScript 支持更少的限制不支持 Proxy 的环境在旧浏览器或 Node.js 环境中,MobX 6 会自动降级到兼容模式。6. 移除的 API以下 API 在 MobX 6 中被移除:isObservableObject → 使用 isObservableintercept → 使用 observe 的拦截功能extras API → 大部分功能已集成到主 APItoJS → 使用 toJSON 或手动转换7. TypeScript 支持的改进MobX 6 对 TypeScript 的支持更加完善:import { makeAutoObservable } from 'mobx';class Store { count: number = 0; firstName: string = 'John'; lastName: string = 'Doe'; constructor() { makeAutoObservable(this); } get fullName(): string { return `${this.firstName} ${this.lastName}`; } increment(): void { this.count++; }}类型推断更加准确,类型定义更加简洁。8. 性能优化MobX 6 引入了多项性能优化:更快的依赖追踪更高效的 computed 缓存更好的批量更新机制减少的内存占用9. 调试体验的改进MobX 6 提供了更好的调试工具:更清晰的错误消息更好的堆栈跟踪改进的 DevTools 支持10. 迁移指南从 MobX 5 迁移到 MobX 6更新依赖npm install mobx@6 mobx-react@6添加 action 装饰器// 之前increment() { this.count++;}// 之后@actionincrement() { this.count++;}使用 makeObservable 或 makeAutoObservableconstructor() { makeAutoObservable(this);}更新配置configure({ enforceActions: 'always'});移除已废弃的 API替换 toJS 为 toJSON更新 isObservableObject 为 isObservable总结MobX 6 是一个重要的版本更新,主要改进包括:强制使用 action 提高可预测性简化的 API 和更好的 TypeScript 支持强制使用 Proxy 提高性能更好的调试体验移除已废弃的 API迁移到 MobX 6 需要一些工作,但带来的改进是值得的。建议新项目直接使用 MobX 6,现有项目逐步迁移。
阅读 0·2月21日 15:51