🚀初试在 React Hooks 中使用 Mobx

2,042 阅读4分钟

全局状态管理库,虽说在项目中不一定需要,但总的来说,还是要掌握一种库比较好,以备不时之需

React 生态中说到全局状态管理库,肯定第一反应就是说 redux,我也不例外;但是为什么没有继续写 redux,因为 redux 确实挺麻烦的,有点不符合个人的思考方式(个人见解,若 redux 也有什么好的写法,也请评论告诉我)

什么是 Mobx

当你想学习一个新的库,当然最好的方式是看文档:MobX 官网

进入官网,Mobx 第一时间告诉了你:State(状态)Actions(动作)Derivations(派生) 这三个概念

  • 定义 State 并使其可观察
  • 使用 Action 更新 State
  • 创建 Derivations 以便自动对 State 变化进行响应

总的来说,就是可以将 State 存在任何数据结构中,再(最好只)用 Action 来定义操作 State 的方法,最后是 State 并且又不需要进一步交互的东西就是 Derivations

State 和 Action 都挺好理解的,就是 Derivations 有点小迷惑,不急,下面就明白了:

Mobx 将 Derivations 分成两种

  • Computed value;总是可以通过纯函数从当前的可观测 State 中派生
  • Reactions;当 State 改变时需要自动运行的副作用(命令式编程和响应式编程之间的桥梁)

你可能还不太明白他们能干啥,给出官网的两个例子吧:

// 通过 computed 对派生值进行建模
import { makeObservable, observable, computed } from "mobx";

class TodoList {
  todos = [];
  get unfinishedTodoCount() {
    return this.todos.filter((todo) => !todo.finished).length;
  }
  constructor(todos) {
    makeObservable(this, {
      todos: observable,

      unfinishedTodoCount: computed,
    });
    this.todos = todos;
  }
}

这里可以看到 unfinishedTodoCount定义的 computed,表示它仅在需要时自动更新,也就是说这个对象或这个对象中的属性被修改时自动更新

官网上描述:如果有人关心其结果时才会更新

上面是关于 computed 的,接下来看关于 reaction 的

reaction 的解释是当 State 改变时需要自动运行的副作用,这不正好可以用在 UI 组件上么?所以官网说最常用的 reaction 形式是 UI 组件

import * as React from "react";
import { render } from "react-dom";
import { observer } from "mobx-react-lite";

const TodoListView = observer(({ todoList }) => (
  <div>
    <ul>
      {todoList.todos.map((todo) => (
        <TodoView todo={todo} key={todo.id} />
      ))}
    </ul>
    Tasks left: {todoList.unfinishedTodoCount}
  </div>
));

const TodoView = observer(({ todo }) => (
  <li>
    <input
      type="checkbox"
      checked={todo.finished}
      onClick={() => todo.toggle()}
    />
    {todo.title}
  </li>
));

const store = new TodoList([
  new Todo("Get Coffee"),
  new Todo("Write simpler code"),
]);

render(<TodoListView todoList={store} />, document.getElementById("root"));

还有更详细的可以看官网,接下来就开始写代码了

在 React 中使用 Mobx

Step 1、准备好 Mobx 环境

下载啥的就不说了,官网介绍的很清楚了

首先在 src 文件夹下创建一个目录 store,再在其中创建 CounterStore.ts,并编写代码:

import { makeAutoObservable } from "mobx";

class CounterStore {
  count = 0;
  info = "Hello,World";
  constructor() {
    makeAutoObservable(this);
  }

  // 让 count 加一
  addCount() {
    this.count++;
  }

  // 重置数据
  reset() {
    this.count = 0;
    this.info = "Hello,World";
  }

  // 改变 info
  changeInfo(str: string) {
    this.info = str;
  }
}

export default CounterStore;

可以看到我这里使用了 makeAutoObservable,解释一下这个

makeAutoObservable 自动把属性、对象、数组、Maps 和 Sets 转化成 observable,也就是说它可以自动推断:

推断规则(官网上的):

  • 所有 自有 属性都成为 observable
  • 所有 getters 都成为 computed
  • 所有 setters 都成为 action
  • 所有 prototype 中的 functions 都成为 autoAction
  • 所有 prototype 中的 generator functions 都成为 flow。(需要注意,generators 函数在某些编译器配置中无法被检测到,如果 flow 没有正常运行,请务必明确地指定 flow 注解。)
  • overrides 参数中标记为 false 的成员将不会被添加注解。例如,将其用于像标识符这样的只读字段。

这样就结束了么?并没有,再次到 store 文件夹下新建一个 index.ts, 代码如下:

// index.ts
import React from "react";
import CounterStore from "./CounterStore";

export const storesContext = React.createContext({
  counterStore: new CounterStore(),
});

export const useStores = () => React.useContext(storesContext);

现在代码结构如下:

这样后,如果其他地方需要用到 Mobx 中的数据,直接使用这里的 useStores 就 Ok 了

Step 2、在代码中使用

首先新建一个 Test 组件,内容如下:

import { observer } from "mobx-react-lite";
import { useStores } from "../store";

const Test = observer(() => {
  const { counterStore } = useStores();

  // 点击 +1 按钮
  const handleCilck = () => {
    counterStore.addCount();
  };

  // 触发 input 的 change 事件
  const inputChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
    counterStore.changeInfo(e.target.value);
  };

  return (
    <div style={{ backgroundColor: "lightblue" }}>
      <h4>组件 Test</h4>
      <div>count为:{counterStore.count}</div>
      <div>info为:{counterStore.info}</div>
      <button onClick={handleCilck}>+1</button>
      <div>
        <input type="text" value={counterStore.info} onChange={inputChange} />
      </div>
    </div>
  );
});

export default Test;

counterStore 中就包括了我们在 CounterStore.ts 中定义的一系列数据

再定义一个组件 OtherTest.tsx,之后好观察效果:

import { useStores } from "../store";
import { observer } from "mobx-react-lite";

const OtherTest = observer(() => {
  const { counterStore } = useStores();

  const resetClick = () => {
    counterStore.reset();
  };

  return (
    <div style={{ backgroundColor: "lightgreen" }}>
      <h4>组件 OtherTest</h4>
      <div>count为:{counterStore.count}</div>
      <div>info为:{counterStore.info}</div>
      <button onClick={resetClick}>重置</button>
    </div>
  );
});

export default OtherTest;

之后到集合到 App.tsx 中观察效果,效果如下:

Step 3、查看结果

一个简单的使用 Mobx 的 Demo 就出来了,看效果:

啧,这样 Mobx 的基本用法就可以了,相比之下还是比 Redux 简单一些

最后

Redux 毋庸置疑肯定是个优秀的库,但是 Mobx 也绝对不差,所以还是选择适合自己的就行

参考链接:

若有不当之处,欢迎评论指出