提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
Mobx6的基本使用
- Mobx概述
- Mobx是什么?
- Mobx和Redux的区别
- Mobx的优点和缺点
- Mobx的使用场景和适用范围
- Mobx基本使用流程
- 安装
- 安装 mobx6
- 安装 React 的集成库
- 使用流程
- 创建`Store`
- 创建`RootStore`
- 在组件中使用
- Mobx的核心概念
- Observable:可观察对象
- Action:操作
- Computed:计算值
- Reactions:响应式副作用
- Observable
- 使用 observable 函数
- 使用 makeObservable 函数
- 使用 makeAutoObservable 函数
- Action
- Action中没有异步操作
- Action中含有异步操作
- Computed
- Reactions
- autorun
- reaction
- when
- Mobx模块化
- 总结
Mobx概述
Mobx是什么?
Mobx是一个简单易用的状态管理库,它提供了一种简洁的方法来管理JavaScript应用程序的状态。通过使用Mobx,您可以轻松地将应用程序状态分解为可观察的部分,并在状态发生变化时自动更新UI。
mobx官网:
https://zh.mobx.js.org/installation.html
Mobx和Redux的区别
Mobx与Redux一样,都是用于管理应用程序状态的库。但是,两者在设计理念和用法上有很大的不同。
Redux是一个严格的状态容器,它使用单向数据流来管理应用程序状态。Redux的设计是基于函数式编程的思想,它通过纯函数来改变状态,然后通过订阅机制来更新UI。
相比之下,Mobx的设计更加灵活,它支持直接修改状态,并且能够自动检测状态的变化并更新UI。与Redux相比,Mobx更加易于使用和理解。
Mobx的优点和缺点
优点
- 易于学习和使用。
- 基于响应式编程的设计,能够自动更新UI。
- 支持直接修改状态,更加灵活。
- 可以在任何JavaScript应用程序中使用,不限于React。
缺点
- 可能会导致性能问题,特别是在处理大型数据集时。
- 不适用于复杂的应用程序状态。
- 可能会导致代码结构变得混乱和难以维护
Mobx的使用场景和适用范围
Mobx适用于任何需要管理JavaScript应用程序状态的场景。特别是在以下情况下,Mobx的优点将更加明显:
- 在React应用程序中管理状态。
- 处理需要实时更新UI的数据。
- 需要处理可变数据集。
Mobx基本使用流程
本文计算器的案例为例
安装
安装 mobx6
npm install mobx
复制
安装 React 的集成库
npm install mobx-react-lite
复制
或
npm install mobx-react
复制
使用流程
创建Store
//store/CounterStore.ts import { makeAutoObservable } from 'mobx' class CounterStore { //定义state //使用 makeObservable 或 makeAutoObservable 绑定之后才是真的state count = 10; constructor() { //参数2:排除属性和方法,{reset:false} //参数3:自动绑定,不指定就只能用 ()=> makeAutoObservable(this, {}, { autoBind: true }) } //定义action increment() { this.count++; } }
复制
创建RootStore
//store/RootStore.ts import { createContext, useContext } from "react"; import CounterStore from "./CounterStore"; class RootStore { counterStotre: CounterStore; constructor() { this.counterStotre = new CounterStore(); } } const store = new RootStore(); //提供初始值,不需要context.Provider const context = createContext(store) //自定义hook function useStore() { return useContext(context); } export default useStore;
复制
在组件中使用
组件必须使用observer
进行包裹,mobx才能正常响应
import React from 'react' import { observer } from 'mobx-react' import useStore from '../../store/RootStore'; function Counter() { let { counterStotre } = useStore(); return ( <div> <h3>计数器</h3> <h4>{counterStotre.count}</h4> <button onClick={() => counterStotre.increment()}>+1</button> </div> ) } export default observer(Counter);
复制
Mobx的核心概念
Observable:可观察对象
可观察对象是MobX的核心概念之一。它是一个包装了数据的对象,当数据发生变化时会自动通知相关的观察者。可以使用observable
、makeObservable
、makeAutoObservable
等方式来创建可观察对象。
import { observable } from 'mobx'; const person = observable({ name: '张三', age: 18, });
复制
import { observable } from 'mobx'; const numbers = observable([1, 2, 3]); // 添加元素 numbers.push(4); // 删除元素 numbers.pop();
复制
Action:操作
Actions是一组用于修改可观察数据的函数。在Mobx中,只有在Actions中修改可观察数据,才能够自动触发更新。
import { observable, action } from 'mobx'; const counter = observable({ count: 0, increment: action(function () { this.count++; }), decrement: action(function () { this.count--; }), });
复制
Computed:计算值
Computed是一种在可观察数据的基础上进行计算的方式。Computed值是基于可观察数据自动计算得出的值,当可观察数据发生变化时,计算值会自动更新。
import { observable, computed } from 'mobx'; const shoppingCart = observable({ items: [], get total() { return this.items.reduce((total, item) => total + item.price * item.quantity, 0); }, });
复制
Reactions:响应式副作用
Reaction是一种响应可观察对象变化的方法,当可观察对象发生变化时,会自动执行Reaction中定义的函数。可以使用autorun
、when
、reaction
等方式来创建Reaction。
import { observable, reaction } from 'mobx'; const person = observable({ name: '张三', age: 18, }); reaction( () => person.name, (name) => console.log(`我的名字是 ${name}。`) ); person.name = '李四'; // 输出:我的名字是李四。
复制
Observable
在 MobX6 中,Observable 是一个非常重要的概念,它可以用来包装 JavaScript 对象、数组、Map、Set、原始类型等数据结构,使其成为可观察对象。当可观察对象中的数据发生变化时,所有使用该数据的部分都会自动更新。
在 MobX6 中,创建 Observable 的方式主要有三种:
使用 observable 函数
import { observable } from "mobx"; const store = observable({ todos: [], filter: "all", get filteredTodos() { return this.todos.filter(todo => { if (this.filter === "completed") { return todo.completed; } else if (this.filter === "active") { return !todo.completed; } else { return true; } }); } });
复制
在这个例子中,我们使用 observable
函数来创建一个可观察对象 store。它有两个属性:todos
是一个空数组,filter
是一个字符串 “all”。还有一个计算属性 filteredTodos
,它根据 todos
和 filter
进行计算
使用 makeObservable 函数
在Mobx6中,可以使用 makeObservable
函数来定义可观察对象。这个函数可以将装饰器的功能与普通的类定义结合起来,从而使Mobx更加易于使用。
import { makeObservable, observable, computed } from "mobx"; class TodoStore { todos = []; filter = "all"; constructor() { makeObservable(this, { todos: observable, filter: observable, filteredTodos: computed, }); } get filteredTodos() { return this.todos.filter(todo => { if (this.filter === "completed") { return todo.completed; } else if (this.filter === "active") { return !todo.completed; } else { return true; } }); } }
复制
使用 makeAutoObservable 函数
使用用 makeAutoObservable
可以进一步对其进行简化
import { makeAutoObservable } from "mobx"; class TodoStore { todos = []; filter = "all"; constructor() { makeAutoObservable(this, {}, { autoBind: true }); } get filteredTodos() { return this.todos.filter(todo => { if (this.filter === "completed") { return todo.completed; } else if (this.filter === "active") { return !todo.completed; } else { return true; } }); } }
复制
Action
在 MobX6 中,Action 是一种用于修改状态的方法。使用 Action,我们可以确保在状态修改过程中,所有相关的观察者都能够正确地被通知到,并且 MobX 会自动地运行相关的 Reaction,从而保证应用程序的状态与用户界面保持同步。
Action中没有异步操作
import { action, makeObservable, observable } from "mobx"; class Counter { count = 0; constructor() { makeObservable(this, { count: observable, increment: action, decrement: action, }); } increment() { this.count++; } decrement() { this.count--; } }
复制
Action中含有异步操作
需要注意的是,如果你在一个 Action 中使用了异步操作(如 Promise
或者 setTimeout
等),你需要使用 runInAction
函数来确保异步操作的结果能够正确地触发 Reaction。
从本质上讲,异步进程在 MobX 中不需要任何特殊处理,因为不论是何时引发的所有 reactions 都将会自动更新。但mobx在异步中直接改变属性的值,会报一个警告。正常mobx处理异步大概有两种方式,个人推荐使用runInAction
处理异步。
//store/CounterStore.ts import { makeAutoObservable, runInAction } from 'mobx' class CounterStore { //observable count = 10; constructor() { //参数2:排除属性和方法,{reset:false} //参数3:自动绑定,不指定就只能用 ()=> makeAutoObservable(this, {}, { autoBind: true }) } //action increment() { this.count++; } //计算属性 get double() { return this.count * 2; } // //处理异步-方式1 decrementAsync1() { setTimeout(this.increment, 1000) } // //处理异步-方式2(推荐这种) decrementAsync2() { setTimeout(() => { runInAction(() => { this.count++; }) }, 1000) } }
复制
下面是官网处理异步的一个经典案例,async/await
+ runInAction
的配合使用,在实际情况中很常用。
import { action, makeObservable, observable, runInAction } from "mobx"; class TodoStore { todos = []; constructor() { makeObservable(this, { todos: observable, addTodo: action, }); } async fetchTodos() { const response = await fetch("https://jsonplaceholder.typicode.com/todos"); const todos = await response.json(); runInAction(() => { this.todos = todos; }); } addTodo(todo) { this.todos.push(todo); } }
复制
Computed
在 MobX6 中,Computed
是一种基于 Observable
创建的衍生值。Computed
可以从 Observable
中获取数据,并根据这些数据生成一个新的值,这个新的值也是一个 Observable
。
使用 Computed
,我们可以避免在代码中手动追踪衍生值的变化,从而简化代码并提高应用程序的性能。
//store/CounterStore.ts import { computed, makeObservable, observable } from "mobx"; class Order { items = []; discountRate = 0.1; constructor() { makeObservable(this, { items: observable, discountRate: observable, totalPrice: computed, discountedPrice: computed, }); } get totalPrice() { return this.items.reduce((total, item) => total + item.price, 0); } get discountedPrice() { return this.totalPrice * (1 - this.discountRate); } }
复制
在这个例子中,我们定义了一个 Order 类,其中 items
和 discountRate
属性都是可观察对象。我们使用 makeObservable
函数将它们转化为可观察对象,将 totalPrice
和 discountedPrice
方法转化为 Computed
。
当我们调用 totalPrice
方法时,它会从 items
中获取数据,并根据这些数据计算出总价。当 items
中的数据发生变化时,totalPrice
会自动重新计算,从而保证总价的正确性。
同样的,当我们调用 discountedPrice
方法时,它会从 totalPrice
和 discountRate
中获取数据,并根据这些数据计算出折扣后的价格。当 items
或 discountRat
e 发生变化时,discountedPric
e 会自动重新计算,从而保证折扣后的价格的正确性。
需要注意的是,如果你想要在 Computed
中访问其他 Observable
,你需要在 makeObservable
或makeAutoObservable
中将这些 Observable
声明为可观察对象,否则 Computed
将无法自动追踪这些 Observable
的变化
Reactions
autorun
autorun
是 MobX6 中的一个 API,它用于创建一个响应式函数,并在数据发生变化时自动执行该函数。autorun
接受一个函数作为参数,该函数中使用的任何响应式数据都会被自动收集,当这些数据发生变化时,autorun
会自动重新运行该函数。
autorun
的语法如下:
autorun(view: (r: IReactionPublic) => any, opts?: IAutorunOptions): IReactionDisposer
复制
其中,view
是一个响应式函数,会被自动执行;options
是一个可选的配置对象,可以设置 name 和 delay 等选项。
下面是一个示例,使用 autorun
创建一个响应式函数:
import { observable, autorun } from 'mobx'; const store = observable({ count: 0, }); autorun(() => { console.log('Count:', store.count); }); store.count = 1; store.count = 2;
复制
在上面的示例中,autorun
创建了一个响应式函数,会在 store.count
发生变化时自动重新执行。当 store.count
分别变为 1 和 2 时,响应式函数会分别输出 Count: 1 和 Count: 2。
需要注意的是,在使用 autorun
时,需要传入一个响应式函数作为参数,该函数中使用的任何响应式数据都会被自动收集,当这些数据发生变化时,autorun
会自动重新运行该函数。此外,autorun
返回一个 disposer
函数,可以用于取消响应式函数的自动执行。
reaction
reaction
是 MobX6 中的一个 API,它用于创建一个响应式函数,并在数据发生变化时自动执行该函数。与 autorun
不同的是,reaction
允许自定义数据变化时执行的操作,并返回一个可用于取消响应式函数的 disposer
函数。
reaction
的语法如下:
reaction<T>( expression: (r: IReactionPublic) => T, effect: (arg: T, prev: T | undefined, r: IReactionPublic) => void, options ?: IReactionOptions ): IReactionDisposer
复制
其中,expression
是一个函数,用于返回一个数据源;effect
是一个函数,用于执行自定义操作;options
是一个可选的配置对象,可以设置 name
和 fireImmediately
等选项。
下面是一个示例,使用 reaction
创建一个响应式函数:
import { observable, reaction } from 'mobx'; const store = observable({ count: 0, }); reaction( () => store.count, (count, prevCount) => { console.log(`Count changed from ${prevCount} to ${count}`); } ); store.count = 1; store.count = 2;
复制
在上面的示例中,reaction
创建了一个响应式函数,会在 store.count
发生变化时自动执行,并输出变化前后的值。当 store.count
分别变为 1 和 2 时,响应式函数会分别输出 Count changed from 0 to 1 和 Count changed from 1 to 2。
需要注意的是,在使用 reaction
时,需要传入两个函数作为参数,一个用于返回数据源,一个用于执行自定义操作。与 autorun
不同的是,reaction
允许自定义数据变化时执行的操作,并返回一个可用于取消响应式函数的 disposer
函数。
下面是一个综合示例:
//store/CounterStore.ts import { autorun, makeAutoObservable, reaction, runInAction } from 'mobx' class CounterStore { //observable count = 10; constructor() { //参数2:排除属性和方法,{reset:false} //参数3:自动绑定,不指定就只能用 ()=> makeAutoObservable(this, {}, { autoBind: true }) } //action increment() { this.count++; } //计算属性 get double() { return this.count * 2; } // //处理异步-方式1 decrementAsync1() { setTimeout(this.increment, 1000) } // //处理异步-方式2(推荐这种) decrementAsync2() { setTimeout(() => { runInAction(() => { this.count++; }) }, 1000) } } let counterStore = new CounterStore(); // 监听属性-1 // 创建时,会先执行一次 autorun(() => { console.log("counter.count", counterStore.count) }) // 监听属性-2 // 创建时,不执行,变化时,才执行 reaction( () => counterStore.count, () => { console.log("count变化了") } ) export default CounterStore; export { counterStore };
复制
//store/RootStore.ts import { createContext, useContext } from "react"; import CounterStore, { counterStore } from "./CounterStore"; class RootStore { counterStore: CounterStore; constructor() { this.counterStore = counterStore; } } const store = new RootStore(); //提供初始值,不需要context.Provider const context = createContext(store) //自定义hook function useStore() { return useContext(context); } export default useStore;
复制
when
when
是 MobX6 中的一个 API,它用于在满足指定条件时执行某些操作。when
会一直等待直到条件变为 true
,然后执行传入的回调函数。
when(predicate: () => boolean, effect?: () => void, options?)
复制
其中,predicate
是一个返回布尔值的函数,表示条件;effect
是一个可选的回调函数,表示当条件满足时要执行的操作;options
是一个可选的配置对象,可以设置 timeout
和 name
等选项。
下面是一个示例,使用 when
来等待一个异步操作完成后再执行某些操作:
import { observable, when } from 'mobx'; const store = observable({ data: null, isLoading: true, async fetchData() { const response = await fetch('/api/data'); const data = await response.json(); this.data = data; this.isLoading = false; }, }); when(() => !store.isLoading, () => { console.log('Data has been loaded:', store.data); }); store.fetchData();
复制
Mobx模块化
//store/Counter.ts import { makeObservable, observable, action, computed, makeAutoObservable, autorun, reaction, runInAction } from 'mobx' class Counter { //observable count = 0; constructor() { // makeObservable(this, { // count: observable, // increment: action, // reset: action, // decrement:action.bound, // double:computed // }) //参数2:排除属性和方法,{reset:false} //参数3:自动绑定,不指定就只能用 ()=> makeAutoObservable(this, {}, { autoBind: true }) } increment() { this.count++; } //action reset() { this.count = 0; } decrement() { this.count--; } //计算属性 get double() { return this.count * 2; } //处理异步-方式1 decrementAsync() { setTimeout(this.increment, 1000) } //处理异步-方式2 decrementAsync2() { setTimeout(() => { runInAction(() => { this.count++; }) }, 1000) } } let myCounter = new Counter(); // 监听属性-1 // 创建时,会先执行一次 autorun(() => { console.log("counter.count", myCounter.count) }) // 监听属性-2 // 创建时,不执行,变化时,才执行 reaction( () => myCounter.count, () => { console.log("count变化了") } ) export default myCounter;
复制
//store/index.ts import { createContext, useContext } from "react"; import myCounter from "./Counter"; class RootStore { counter = myCounter; } const store = new RootStore; //提供初始值,可以不使用context.Provider const context = createContext(store) //自定义hook function useStore() { return useContext(context); } export default useStore;
复制
import React from 'react' import { observer } from 'mobx-react' import useStore from './store' function App() { let store = useStore(); return ( <div> <h3>计数器</h3> <h4>{store.counter.count}</h4> <h4>{store.counter.double}</h4> <button onClick={() => store.counter.increment()}>+1</button> <button onClick={store.counter.decrement}>-1</button> <button onClick={() => store.counter.reset()}>重置</button> <button onClick={() => store.counter.decrementAsync()}>异步+1</button> <button onClick={() => store.counter.decrementAsync2()}>异步+1</button> </div> ) } export default observer(App);
复制
总结
Mobx6是一个非常强大的状态管理库,它可以帮助我们轻松地管理应用程序的状态。在使用Mobx6时,我们应该遵循一些最佳实践,例如避免使用动态可观察数据、使用Computed
代替派生状态、使用Reactions处理异步行为等。此外,我们还可以利用Mobx6的一些新特性,例如使用makeObservable
函数、可观察对象的类型推导、使用@action.bound
代替装饰器等。虽然Mobx6具有很多优点,但仍然存在一些局限性,例如不支持自动引用计数、不支持观察Map
和Set
对象的键等。因此,在使用Mobx6时,我们应该了解这些局限性,并根据实际情况进行选择。