提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
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时,我们应该了解这些局限性,并根据实际情况进行选择。