MobX6 中新增的关键功能解析 ( autoRun、reaction、runInAction )

1,686 阅读5分钟

MobX 是一款通过透明地应用函数式响应式编程(FRP)来使状态管理变得简单和可扩展的库。在 MobX 6 中,引入了一些新的功能和改进,其中包括 autoRun、reaction 和 runInAction。下面我们将逐一深入探讨这些功能,并通过代码示例来展示它们的使用方法和优势。

一、autoRun

autoRun 是 MobX 6 中的一个新功能,它允许你自动跟踪响应式数据的变化,并在这些数据发生变化时运行特定的函数。autoRun 返回一个处理函数,可以用来停止或重新启动反应。

下面是一个使用 autoRun 的简单示例:

import { observable, autoRun, enableLogging } from 'mobx';

enableLogging(); // 开启日志,便于观察状态变化

const store = observable({
    count: 0
});

const disposer = autoRun(() => {
    console.log(`Count has changed: ${store.count}`);
});

// 改变状态,会触发 autoRun 中的函数
store.count = 1; // 输出:Count has changed: 1

// 当不再需要反应时,可以使用 disposer 函数来停止它
disposer();
store.count = 2; // 无输出,因为反应已被停止

在这个例子中,我们创建了一个可观察的对象 store,并使用 autoRun 来自动跟踪 store.count 的变化。当我们改变 store.count 的值时,autoRun 中的函数会自动执行。

二、reaction

reaction 是 MobX 中用于创建响应式反应的另一个重要函数。与 autoRun 不同,reaction 允许你定义一个更复杂的反应逻辑,并且只在该逻辑的结果发生变化时触发。

下面是一个使用 reaction 的示例:

import { observable, reaction } from 'mobx';

const store = observable({
    todos: []
});

reaction(
    () => store.todos.length, // 跟踪 todos 长度的变化
    (length, reaction) => {
        console.log(`Todo count changed: ${length}`);
        if (length > 0) {
            console.log("There are todos to complete!");
        } else {
            console.log("No todos left!");
        }
    }
);

// 添加和删除 todos,观察控制台的输出变化
store.todos = [{ text: 'Learn MobX' }]; // 输出:Todo count changed: 1,以及 "There are todos to complete!"
store.todos = []; // 输出:Todo count changed: 0,以及 "No todos left!"

在这个例子中,我们使用 reaction 来跟踪 store.todos 数组的长度的变化,并在长度发生变化时执行特定的逻辑。

三、runInAction

runInAction 是 MobX 6 中的一个重要功能,它允许你在一个单一的、原子的、同步的事务中执行多个状态更改。这可以确保在更改过程中不会触发任何观察者,直到所有更改都完成为止。

下面是一个使用 runInAction 的示例:

import { observable, runInAction } from 'mobx';

const store = observable({
    count1: 0,
    count2: 0
});

runInAction(() => {
    store.count1 = 1;
    store.count2 = 2;
});
// 在这个 runInAction 块中,任何观察者都不会被触发,直到整个块执行完毕。

在这个例子中,我们使用 runInAction 来确保在更改 count1count2 时不会触发任何中间状态的观察者。

再比如,向被监控的数组中添加元素的时候,可以使用 runInAction 来避免组件的多次渲染:

async loadTodos() { 
    let todos = await axios.get("http://localhost:3005/todos").then(response => response.data);
    runInAction(() => todos.forEach(todo => this.todos.push(todo))) 
}

四、对比 runInAction 和之前使用 generator 实现相同功能的代码差异

在 MobX 5 及更早版本中,为了实现类似 runInAction 的功能,我们可能需要使用复杂的 generator 语法和 yield 关键字。然而,这种方法相对繁琐且容易出错。相比之下,MobX 6 中的 runInAction 提供了一个更简洁、更直观的方式来执行原子性的状态更改。

下面是一个使用 MobX 5 的 generator 语法的示例:

import { observable, action, runInTransaction } from 'mobx'; // MobX 5 导入方式可能有所不同

const store = observable({
    count1: 0,
    count2: 0,
    *incrementBoth() { // 使用 generator 语法定义动作
        yield this.count1++; // 使用 yield 来确保动作的原子性
        yield this.count2++; // 同上
    }
});

runInTransaction(() => { // 使用 runInTransaction 来执行 generator 动作
    store.incrementBoth().next(); // 需要手动调用 next() 来执行每一步,较为繁琐
    store.incrementBoth().next(); // 同上,容易出错或遗漏步骤
});

相比之下,使用 MobX 6 的 runInAction 语法更加简洁和直观:

import { observable, runInAction } from 'mobx'; // MobX 6 导入方式更简洁统一

const store = observable({
    count1: 0,
    count2: 0,
});

runInAction(() => { // 使用 runInAction 来确保动作的原子性,无需 generator 语法和 yield 关键字
    store.count1++; // 直接在 runInAction 块中执行状态更改即可
    store.count2++; // 同上,更加简洁直观且不易出错遗漏步骤。
});

在 React 中使用这三个新增关键功能

下面我将通过一个简单的 React 项目示例来说明如何在 React 中使用这些 API。

设置项目

首先,确保你的项目中已经安装了 React、MobX 和 MobX React。你可以使用以下命令来安装它们(如果尚未安装):

npm install react mobx mobx-react

创建一个简单的 MobX Store

import { observable, autoRun, reaction, runInAction, makeAutoObservable } from 'mobx';

class TodoStore {
    todos = observable([
        { id: 1, text: 'Buy milk', completed: false },
        { id: 2, text: 'Go to the gym', completed: false },
        { id: 3, text: 'Write article', completed: false },
    ]);

    constructor() {
        makeAutoObservable(this);

        // 使用 autoRun 自动跟踪 todos 的变化,并打印信息
        autoRun(() => {
            console.log('Todos have changed:', this.todos.map(todo => todo.text));
        });

        // 使用 reaction 对 todos 的数量进行反应
        reaction(
            () => this.todos.length,
            (length, reaction) => {
                console.log(`Todo count is now: ${length}`);
            }
        );
    }

    addTodo(text) {
        runInAction(() => {
            this.todos.push({
                id: Math.random(),
                text: text,
                completed: false
            });
        });
    }

    toggleTodo(todo) {
        runInAction(() => {
            todo.completed = !todo.completed;
        });
    }
}

export default new TodoStore();

在上面的代码中,我们创建了一个 TodoStore 类,该类具有可观察的 todos 数组。我们在构造函数中使用 autoRun 来自动跟踪 todos 数组的变化,并且每当数组发生变化时都会在控制台打印信息。我们还使用 reaction 来跟踪 todos 数组的长度,并在长度变化时在控制台中打印新的长度。

addTodotoggleTodo 方法都使用了 runInAction 来确保添加或切换待办事项的状态变化是原子的,并且在这过程中不会触发任何观察者,直到整个动作完成。

在 React 组件中使用 MobX Store

import React from 'react';
import { observer } from 'mobx-react';
import todoStore from './TodoStore';

const TodoList = observer(() => {
    return (
        <div>
            <h2>Todo List</h2>
            <ul>
                {todoStore.todos.map(todo => (
                    <li key={todo.id}>
                        <input
                            type="checkbox"
                            checked={todo.completed}
                            onChange={() => todoStore.toggleTodo(todo)}
                        />
                        {todo.text}
                    </li>
                ))}
            </ul>
            <button onClick={() => todoStore.addTodo('New Task')}>Add Todo</button>
        </div>
    );
});

export default TodoList;

在上面的 React 组件中,我们使用了 observer 高阶组件来让组件能够响应 MobX store 中的状态变化。每当 todos 数组或其元素发生变化时,TodoList 组件都会自动重新渲染。

通过点击复选框来切换待办事项的完成状态,或者点击 "Add Todo" 按钮来添加新的待办事项,这些操作都会触发我们在 TodoStore 中定义的 autoRunreaction,从而在控制台中看到相应的输出。

这样,我们就成功地在 React 项目中集成了 MobX,并通过 autoRunreactionrunInAction 这些 API 实现了状态的高效管理。