引言
MobX 是一个简单、可扩展的状态管理库,用于构建 React 应用。它通过将应用程序的状态与界面进行连接,使得状态的变化能够自动地反映在界面上,从而简化了状态管理的过程。相比于其他复杂的状态管理解决方案,MobX的设计理念是让状态管理变得简单而直观。
MobX 状态派生模型:
常用的注解装饰器
在较低版本的mobx中使用如下方式的注解
1、@observable
@observable
是 MobX 中定义可观察对象的一种方式。当我们使用 @observable
对一个属性进行修饰时,该属性就成为了一个可观察对象,我们可以在它上面绑定 reaction
或者 computed
函数来实现对数据的监听和对数据的计算。 我们通过例子进一步了解@observable
jsx
import { observable } from 'mobx';
class Counter {
@observable count = 0;
increment() {
this.count++;
}
}
const counter = new Counter();
console.log(counter.count); // 输出: 0
counter.increment();
console.log(counter.count); // 输出: 1
通过上述示例,我们可以看到 @observable
的作用是将属性转化为可观察状态,使我们能够跟踪该属性的变化。
2、@action
在 MobX 中,@action
用于将方法标记为一个动作(action)。通过使用 @action
装饰器,我们可以确保这个方法内部对可观察数据的修改是被允许的,同时也能够通知系统进行相应的响应。
jsx
import { observable, action } from "mobx";
class Store {
@observable count = 0;
@action increment() {
this.count++;
}
}
const myStore = new Store();
myStore.increment(); // 自动触发更新
在上面的例子中,我们使用@action
装饰器将increment
方法声明为动作。当调用increment
方法时,count
属性的值会自动递增,并触发相关组件的更新。
3、@computed
@computed
用于将函数或 getter 方法转换为可观察的计算属性。通过 @computed
装饰器,我们可以定义一个响应式的计算属性,当其依赖的可观察数据发生变化时,它会自动重新计算并更新自身的值。
jsx
import { observable, computed } from "mobx";
class Store {
@observable items = [];
@computed get itemCount() {
return this.items.length;
}
}
const myStore = new Store();
myStore.items.push("item 1");
console.log(myStore.itemCount); // 输出:1
在上面的例子中,我们使用@computed
装饰器将itemCount
方法声明为衍生数据。它根据items
数组的长度计算得出,当items
发生变化时,itemCount
会自动重新计算。
是否可以在计算属性的
get
方法中调用其他计算属性?
当然是可以的!,举个例子加深理解:
jsx
import { observable, computed } from "mobx";
class User {
@observable
firstName = "John";
@observable
lastName = "Doe";
@computed
get fullName() {
return this.firstName + " " + this.lastName;
}
@computed
get displayName() {
return "User: " + this.fullName;
}
}
在上面的代码中,我们声明了一个计算属性displayName
,通过在displayName()
方法中调用this.fullName
,我们可以在计算属性中调用其他计算属性来获取所需的值。
4、@observer
@observer
允许我们将 React 组件连接到 MobX 状态树,从而实现响应式的界面。通过使用 @observer
,我们可以将组件转化为响应式组件,使其在状态发生变化时自动重新渲染,无需手动管理繁琐的更新逻辑。用法如下:
jsx
import React from 'react';
import { observer } from 'mobx-react';
@observer
class MyComponent extends React.Component {
// ...
render() {
// ...
}
}
Mobx 6.x 中创建可观察状态
在Mobx 6.x或更高版本中,推荐使用makeObservable
和 makeAutoObservable
将普通 JavaScript 对象或类转换为可观察对象的函数。它们可以自动地将类的属性声明为可观察状态,并根据需要生成相应的 getter 和 setter 方法。
1. makeObservable
makeObservable
是 Mobx 中用来将普通对象转换成可观察对象的函数。需要手动定义每个属性和方法的可观察类型。
makeObservable(target, annotations?, options?)
- target : 要转换成可观察对象的目标对象,一般是
this
。 - annotations : 一个对象,用来指定哪些属性需要被转换成可观察属性,以及它们的行为和类型。它将会为每一个成员映射 注解;以下是它可能的常用元注解(定义注解的注解):
deep
: 表示该属性是否进行深度观察。如果值为true
,那么当该属性是对象或数组时,其子属性也会被观察。默认为false
。default
: 该属性的默认值,在属性值未初始化时使用。ref
: 表示该属性是否为一个可变的引用(mutable)。
- options :配置对象,它可以设置一些选项来影响注解的行为。有两个常用的选项:
autoBind
:这个选项默认为false
,表示不自动绑定 action 方法的 this 指向。如果设置为 true,表示使 action 方法始终拥有正确的 this 指向,而不需要使用 bind 或箭头函数来绑定。proxy
:这个选项默认为true
,表示使用 Proxy 包装来创建 observable 属性。如果设置为 false,表示禁用 Proxy 包装,使用 defineProperty 来创建 observable 属性。这样可以提高兼容性,但是会失去一些特性,例如动态添加或删除属性。name: <string>
: 为对象提供一个在错误消息和反射 API 中打印的调试名称。
方便刚接触的小伙伴加深印象,特别的列举几个元注解的使用方式:
deep
jsx
import { observable, autorun } from "mobx";
const shallowArray = observable ([{ x: 1 }], { deep: false });
const deepArray = observable ([{ x: 1 }]);
autorun (() => {
console.log (shallowArray[0]);
});
autorun (() => {
console.log (deepArray[0]);
});
shallowArray[0].x = 2; // 不触发反应
deepArray[0].x = 2; // 触发反应
shallowArray[0] = { x: 3 }; // 触发反应
deepArray[0] = { x: 3 }; // 触发反应
default
jsx
class CounterStore {
// 使用 default 注解定义一个可观察的数值属性
count = 0
constructor() {
makeObservable(this, {
// 此行代码相当于:count: observable
count: observable.default
})
}
// 使用 default 注解定义一个计算属性
get double() {
return this.count * 2
}
// 使用 default 注解定义一个 action 方法
increment() {
this.count++
}
}
ref
jsx
import { observable, autorun } from "mobx";
const person = observable ({
name: "Alice",
address: { city: "Beijing" }
}, {
address: observable.ref // 引用的 observable
});
autorun (() => {
console.log (person.address);
});
person.address.city = "Shanghai"; // 不触发反应
person.address = { city: "Shenzhen" }; // 触发反应
// 另一个例子,使用Person类
import { makeObservable, observable, reaction } from "mobx";
class Person {
name = "";
address = {};
constructor(name, city) {
// 将 address 属性设置为引用的 observable
makeObservable(this, {
name: observable,
address: observable.ref
});
this.name = name;
this.address.city = city;
}
}
const person = new Person("Tom", "Tokyo");
// 创建一个 reaction,当 person.address 发生变化时打印出新的地址
reaction(
() => person.address, // 观察 person.address 的变化
(newAddress) => console.log(newAddress) // 当 person.address 变化时执行的动作
);
// 修改 person.address.city,不会触发 reaction
person.address.city = "Osaka";
// 替换 person.address 对象,会触发 reaction,并打印出新的地址
person.address = { city: "Kyoto" };
注意:
observable.ref
是一种禁用自动转换的注解,它只会创建一个可观察的引用,而不会改变赋值给字段的值本身。这意味着如果赋值的值是一个对象、数组、Map 或 Set,那么它们的属性或元素不会变成可观察的。
下面是一个使用 makeObservable
的完整示例:
jsx
import { makeObservable, observable, computed, action } from "mobx";
class Counter {
count = 0;
multiplier = 2;
address = { city: "Beijing", country: "China", };
constructor() {
makeObservable(this, {
count: observable,
multiplier: observable,
doubledCount: computed,
increment: action,
address: observable.shallow,
});
}
get doubledCount() {
return this.count * this.multiplier;
}
increment() {
this.count++;
}
}
const counter = new Counter();
2. makeAutoObservable
makeAutoObservable
用于自动将对象的属性和方法标记为可观察的。这使得当这些属性或方法发生变化时,MobX 可以追踪并触发相关的副作用(如重新渲染 UI)。
makeAutoObservable(target, overrides?, options?)
- target: 要转化的目标对象,通常是 this。
- overrides: 覆盖默认的推断规则,可以指定某些属性或方法的注解,或者用 false 排除它们。
- options: 配置对象,它可以设置一些选项来影响注解的行为。有以下常用的选项:
autoBind
:这个选项默认为true
,表示不自动绑定 action 方法的 this 指向。如果设置为 true,表示使 action 方法始终拥有正确的 this 指向,而不需要使用 bind 或箭头函数来绑定。proxy
:这个选项默认为false
,表示使用 Proxy 包装来创建 observable 属性。如果设置为 false,表示禁用 Proxy 包装,使用 defineProperty 来创建 observable 属性。这样可以提高兼容性,但是会失去一些特性,例如动态添加或删除属性。name: <string>
: 为对象提供一个在错误消息和反射 API 中打印的调试名称。
以下是一个使用makeAutoObservable
的示例:
jsx
class Person {
firstName = "Mob"
lastName = "X"
age = 0
hobbies = []
constructor() {
makeAutoObservable(this, {
// 排除 age 不转化为 observable
age: false,
// 排除 hobbies 不转化为 observable
hobbies: false
})
}
get fullName() {
return this.firstName + " " + this.lastName
}
setFirstName(name) {
this.firstName = name
}
setLastName(name) {
this.lastName = name
}
}
第二个参数overrides
可能的属性或方法的注解有以下几种:
- observable: 定义一个存储状态的可追踪字段,可以是普通值,对象,数组,Map 或 Set。
- computed: 定义一个从状态派生出新值并缓存其输出的 getter。
- action: 定义一个可以修改状态的方法。
- autoAction: 定义一个可以修改状态的方法,但是不需要显式地使用 runInAction 包装。
- flow: 定义一个可以修改状态的生成器函数,可以使用 yield 暂停和恢复执行。
- false: 排除一个属性或方法,不转化为可观察的。
3、第三个参数 options
makeObservable
和 makeAutoObservable
都是用于创建可观察状态的函数,它们都可以接受第三个参数,也就是 options 参数。其中,autoBind 和 proxy 是两个常用的选项。
- autoBind:这个选项可以设置为 true,使 action 方法始终拥有正确的 this 指向,而不需要使用 bind 或箭头函数来绑定。
jsx
import { makeAutoObservable } from "mobx"
class CounterStore {
// 定义一个可观察的数值属性
count = 0
constructor() {
// 使用 makeAutoObservable 自动转化 this 对象的属性和方法,并设置 autoBind 为 true
makeAutoObservable(this, undefined, { autoBind: true })
}
// 定义一个 action 方法,不需要使用 bind 或箭头函数来绑定 this
increment() {
this.count++
}
}
// 或者使用 makeObservable 手动转化,并设置 autoBind 为 true
import { makeObservable, observable, action } from "mobx"
class CounterStore {
// 定义一个可观察的数值属性
count = 0
constructor() {
// 使用 makeObservable 转化 this 对象的属性和方法,并设置 autoBind 为 true
makeObservable(this, {
count: observable,
increment: action
}, { autoBind: true })
}
// 定义一个 action 方法,不需要使用 bind 或箭头函数来绑定 this
increment() {
this.count++
}
}
- proxy:这个选项可以设置为 false,禁用 Proxy 包装,使用 defineProperty 来创建 observable 属性。这样可以提高兼容性,但是会失去一些特性,例如动态添加或删除属性。
jsx
import { makeAutoObservable } from "mobx"
class BoxStore {
// 定义一个可观察的对象属性
box = { width: 0, height: 0 }
constructor() {
// 使用 makeAutoObservable 自动转化 this 对象的属性和方法,并设置 proxy 为 false
makeAutoObservable(this, undefined, { proxy: false })
}
}
// 或者使用 makeObservable 手动转化,并设置 proxy 为 false
import { makeObservable, observable } from "mobx"
class BoxStore {
// 定义一个可观察的对象属性
box = { width: 0, height: 0 }
constructor() {
// 使用 makeObservable 转化 this 对象的属性和方法,并设置 proxy 为 false
makeObservable(this, {
box: observable
}, { proxy: false })
}
}
- name: 表示给该属性一个名称,在控制台中,可以看到可观察的属性或方法的名称,方便在调试时更好地识别它。
jsx
class Person {
// 使用 name 注解给 firstName 属性指定一个名称
firstName = observable("Michel", { name: "myfirstName" });
// 不使用 name 注解
lastName = observable("Weststrate");
constructor() {
// 使用 makeObservable 函数来使类实例具有可观察性和反应性
// 使用 name 选项来给类实例指定一个名称
makeAutoObservable(
this,
undefined,
{ name: "myPerson" }
);
}
}
// 或者使用 makeObservable
class Person {
// 使用 name 注解给 firstName 属性指定一个名称
firstName = observable("Michel", { name: "myfirstName" });
// 不使用 name 注解
lastName = observable("Weststrate");
constructor() {
// 使用 makeObservable 函数来使类实例具有可观察性和反应性
// 使用 name 选项来给类实例指定一个名称
makeObservable(
this,
{ firstName: observable, lastName: observable },
{ name: "myPerson" }
);
}
}
name注解的作用:
- 在控制台中,可以看到可观察的属性或方法的名称,而不是默认的匿名函数或对象。这样可以更容易地定位问题或查看状态变化。
- 在 MobX 开发者工具中,可以看到可观察的属性或方法的名称,而不是默认的
observable
或action
。这样可以更清晰地展示状态树或动作日志。 - 在 MobX 跟踪器中,可以看到可观察的属性或方法的名称,而不是默认的
observable
或action
。这样可以更方便地分析性能或依赖关系。
注意事项
makeObservable
需要手动指定要标记的属性和方法,并通过传递修饰器来标记。makeAutoObservable
不需要手动指定要标记的属性和方法,会自动检测类中的所有属性并进行标记。- 它们都是在类的构造函数中使用的,并且只能在构造函数中调用一次。它们会修改原始对象并添加属性拦截器来实现 MobX 的响应式行为。
- 当使用
makeObservable
或makeAutoObservable
将类的属性声明为可观察状态时,它们会自动为每个属性生成相应的 getter 和 setter 方法。
以下是一个示例,演示了如何使用 makeObservable
和 makeAutoObservable
自动将属性声明为可观察状态并生成 getter 和 setter 方法:
jsx
import { makeObservable, makeAutoObservable, observable } from 'mobx';
class StoreWithMakeObservable {
value = 0;
constructor() {
makeObservable(this, {
value: observable,
});
}
}
class StoreWithMakeAutoObservable {
value = 0;
constructor() {
makeAutoObservable(this);
}
}
const store1 = new StoreWithMakeObservable();
const store2 = new StoreWithMakeAutoObservable();
console.log(store1.value); // 输出:0
store1.value = 10;
console.log(store1.value); // 输出:10
console.log(store2.value); // 输出:0
store2.value = 20;
console.log(store2.value); // 输出:20
Mobx 中的异步操作
在处理异步操作时,MobX 提供了 runInAction
函数,它用于确保在 action 中执行的异步操作能够正确追踪状态的变化。通常,异步操作包括网络请求、定时器等,这些操作可能在当前 action 执行结束后才完成。
jsx
// 请在较低版本中执行以下代码
import { observable, action, runInAction } from 'mobx';
class UserStore {
@observable user = null;
@action
async fetchUser() {
try {
const response = await fetch('https://api.example.com/user');
const userData = await response.json();
runInAction(() => {
this.user = userData;
});
} catch (error) {
runInAction(() => {
// 处理错误
console.error('Error fetching user:', error);
});
}
}
}
const userStore = new UserStore();
在上述示例中,runInAction
函数用于包装异步操作。在 runInAction
的回调函数内部,我们可以安全地修改状态 。这确保了状态修改 发生在 action
内部,以便 MobX 能够正确地追踪状态的变化。
注意: runInAction
可以嵌套使用,以包装多个状态修改操作。
Mobx 管理 React 中的状态
如果当前react项目需要启用mobx管理状态,则需要用到Provider
和@inject
两个API。用法如下:
Provider
是mobx-react
库中的一个 React 组件,用于在应用程序中提供 MobX 存储的状态。它通常与@inject
装饰器一起使用,以确保组件可以访问和响应 MobX 存储的状态。
创建 Provider:
首先,我们需要使用 Provider
组件包装整个 React 应用的根组件。这通常发生在应用的入口文件中。例如:
jsx
import React from 'react';
import { Provider } from "mobx-react"
import todoStore from './todoStore'
class App extends Component{
render(){
return(
<Provider store={...todoStore}>
<ToDoApp/>
</Provider>
)
}
}
使用 @inject
:
在需要访问 MobX 存储的状态的组件中,你可以使用 @inject
装饰器来指定要注入的存储的名称,然后在组件中使用该存储的状态。示例中的 @inject('todoStore')
表示注入名为 todoStore
的 MobX 存储。
jsx
import React from 'react';
import { Provider, inject, observer } from 'mobx-react';
import todoStore from './todoStore';
@inject('todoStore')
@observer
class TodoList extends React.Component {
handleAddTodo = () => {
const { todoStore } = this.props;
todoStore.addTodo('New Todo');
}
render() {
const { todoStore } = this.props;
return (
<div>
<ul>
{todoStore.todos.map((todo, index) => (
<li key={index}>{todo.text}</li>
))}
</ul>
<button onClick={this.handleAddTodo}>Add Todo</button>
</div>
);
}
}
在这里给出一个TodoStore示例,方便我们理解与掌握:
jsx
import { observable, action, makeObservable } from 'mobx';
class TodoStore {
todos = [];
constructor() {
makeObservable(this, {
todos: observable,
addTodo: action,
});
}
addTodo(text) {
this.todos.push({ text, completed: false });
}
}
const todoStore = new TodoStore();
export default todoStore;
注意事项:
- 我们可以同时提供多个存储给
Provider
,只需添加多个属性即可,例如<Provider store1={store1} store2={store2}>
。 @inject
装饰器的参数需要与我们在Provider
中提供的属性名称一致,以便注入正确的存储。
总结
- Mobx 需要我们使用装饰器 或者
makeAutoObservable
等方法来标记可观察对象,动作和计算属性。 - Mobx 需要我们使用特定的 API 来修改可观察对象,例如
observable
、action
、computed
等。如果直接使用赋值运算符或者原生数组方法,可能会导致 Mobx 无法检测到数据的变化。 - Mobx 需要我们使用 observer 高阶组件或者 useObserver 钩子函数来包裹需要响应数据变化的组件。如果忘记了这一步,可能会导致组件无法正确地重新渲染。
想要更加深入了解的小伙伴可以参考官方文档。