看完你就是最了解Mobx的人

引言

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或更高版本中,推荐使用makeObservablemakeAutoObservable 将普通 JavaScript 对象或类转换为可观察对象的函数。它们可以自动地将类的属性声明为可观察状态,并根据需要生成相应的 getter 和 setter 方法。

1. makeObservable

makeObservable 是 Mobx 中用来将普通对象转换成可观察对象的函数。需要手动定义每个属性和方法的可观察类型。

makeObservable(target, annotations?, options?)

  1. target : 要转换成可观察对象的目标对象,一般是this
  2. annotations : 一个对象,用来指定哪些属性需要被转换成可观察属性,以及它们的行为和类型。它将会为每一个成员映射 注解;以下是它可能的常用元注解(定义注解的注解):
    • deep 表示该属性是否进行深度观察。如果值为 true,那么当该属性是对象或数组时,其子属性也会被观察。默认为 false
    • default 该属性的默认值,在属性值未初始化时使用。
    • ref 表示该属性是否为一个可变的引用(mutable)。
  3. 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

makeObservablemakeAutoObservable 都是用于创建可观察状态的函数,它们都可以接受第三个参数,也就是 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 开发者工具中,可以看到可观察的属性或方法的名称,而不是默认的 observableaction。这样可以更清晰地展示状态树或动作日志。
  • 在 MobX 跟踪器中,可以看到可观察的属性或方法的名称,而不是默认的 observableaction。这样可以更方便地分析性能或依赖关系。

注意事项

  • makeObservable需要手动指定要标记的属性和方法,并通过传递修饰器来标记。
  • makeAutoObservable不需要手动指定要标记的属性和方法,会自动检测类中的所有属性并进行标记。
  • 它们都是在类的构造函数中使用的,并且只能在构造函数中调用一次。它们会修改原始对象并添加属性拦截器来实现 MobX 的响应式行为。
  • 当使用 makeObservablemakeAutoObservable 将类的属性声明为可观察状态时,它们会自动为每个属性生成相应的 getter 和 setter 方法。

以下是一个示例,演示了如何使用 makeObservablemakeAutoObservable 自动将属性声明为可观察状态并生成 gettersetter 方法:

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。用法如下:

Providermobx-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 来修改可观察对象,例如 observableactioncomputed 等。如果直接使用赋值运算符或者原生数组方法,可能会导致 Mobx 无法检测到数据的变化。
  • Mobx 需要我们使用 observer 高阶组件或者 useObserver 钩子函数来包裹需要响应数据变化的组件。如果忘记了这一步,可能会导致组件无法正确地重新渲染。

想要更加深入了解的小伙伴可以参考官方文档

相关推荐
小牛itbull4 分钟前
ReactPress:重塑内容管理的未来
react.js·github·reactpress
FinGet11 小时前
那总结下来,react就是落后了
前端·react.js
王解15 小时前
Jest项目实战(2): 项目开发与测试
前端·javascript·react.js·arcgis·typescript·单元测试
AIoT科技物语1 天前
免费,基于React + ECharts 国产开源 IoT 物联网 Web 可视化数据大屏
前端·物联网·react.js·开源·echarts
初遇你时动了情1 天前
react 18 react-router-dom V6 路由传参的几种方式
react.js·typescript·react-router
番茄小酱0011 天前
ReactNative中实现图片保存到手机相册
react native·react.js·智能手机
王解1 天前
Jest进阶知识:深入测试 React Hooks-确保自定义逻辑的可靠性
前端·javascript·react.js·typescript·单元测试·前端框架
小牛itbull1 天前
ReactPress—基于React的免费开源博客&CMS内容管理系统
前端·react.js·开源·reactpress
~甲壳虫2 天前
react中得类组件和函数组件有啥区别,怎么理解这两个函数
前端·react.js·前端框架
用户8185216881172 天前
react项目搭建create-router-dom,redux详细解说
react.js