看完你就是最了解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 钩子函数来包裹需要响应数据变化的组件。如果忘记了这一步,可能会导致组件无法正确地重新渲染。

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

相关推荐
wakangda1 小时前
React Native 集成原生Android功能
javascript·react native·react.js
秃头女孩y7 小时前
【React中最优雅的异步请求】
javascript·vue.js·react.js
前端小小王13 小时前
React Hooks
前端·javascript·react.js
迷途小码农零零发14 小时前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
不是鱼18 小时前
构建React基础及理解与Vue的区别
前端·vue.js·react.js
飞翔的渴望21 小时前
antd3升级antd5总结
前端·react.js·ant design
╰つ゛木槿1 天前
深入了解 React:从入门到高级应用
前端·react.js·前端框架
用户30587584891251 天前
Connected-react-router核心思路实现
react.js
哑巴语天雨2 天前
React+Vite项目框架
前端·react.js·前端框架
初遇你时动了情2 天前
react 项目打包二级目 使用BrowserRouter 解决页面刷新404 找不到路由
前端·javascript·react.js