Mobx状态管理,如何实现 Mobx + React 版本的 todoList ?
发布于 作者:苏南大叔 来源:程序如此灵动~
还是接着分析mobx官方教程,本文描述官方的React版本的todoList。那么,mobx是如何驱动react组件里面的数据的呢?如何让每个引用都变得Reactive的呢?这就是本文要讨论的问题。

苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。本文测试环境:nodejs@20.18.0,create-react-app@5.0.1,react@18.3.1,mobx@6.13.5。当react碰到了mobx,就产生了本文的写在背景信息。
前文回顾
普通非React版本的todoList,Mobx官方教程的修改版,参考如下:
本文的例子中,使用props传递store,使用了useCallback包装传递函数。参考文章:

Store
这个Store.js和原来没有React的时候,代码基本一致,其实就是一个被Mobx观察过的普通类。基于官方的代码,略有改动。
import { makeObservable, observable, computed, action, autorun } from "mobx";
class TodoStore {
todos = [];
pendingRequests = 0;
constructor() {
makeObservable(this, {
todos: observable,
pendingRequests: observable,
completedTodosCount: computed,
report: computed,
addTodo: action,
addTodoSync: action,
modify: action,
});
// 这是另外一个函数包装 autorun()
autorun(() => console.log(this.report));
}
get completedTodosCount() {
return this.todos.filter((todo) => todo.completed === true).length;
}
get report() {
if (this.todos.length === 0) return "";
const nextTodo = this.todos.find((todo) => todo.completed === false);
const percent = `进度:${this.completedTodosCount}/${this.todos.length}`;
return percent + `,准备:${nextTodo ? nextTodo.name : "<无>"}`;
}
addTodo(name) {
if (!name || name.trim() === "") {
return;
}
this.todos.push({
name,
completed: false,
});
}
addTodoSync(name, timeout = 1000) {
if (!name || name.trim() === "") {
return;
}
this.pendingRequests++;
let that = this;
setTimeout(
action(() => {
that.addTodo(name);
that.pendingRequests--;
}),timeout
);
}
modify(id, info) {
this.todos[id] = { ...this.todos[id], ...info };
}
}
window.store = new TodoStore();
export default window.store;在这里,把store挂在window.上的主要目的:是想留着直接在f12里面调试。

这个代码中,增加了两个action:
- 一个是对官方的额外远程加载
todo的代码封装addTodoSync()。 - 另外一个是对
todo自身字段修改的函数modify()。
布局相关逻辑
App.js:
import { observer } from "mobx-react";
import React, { useCallback } from "react";
import "./App.css";
import RenderCount from "./RenderCount";
import store from "./Store";
const TodoItem = observer(({ modify, id, todo }) => {
console.log("item", id, todo);
const onToggleCompleted = () => {
modify(id, { completed: !todo.completed });
};
const onRename = () => {
modify(id, { name: prompt("任务名称", todo.name) || todo.name });
};
return (
<li onDoubleClick={onRename}>
<input
type="checkbox"
checked={todo.completed}
onChange={onToggleCompleted}
/>
{todo.name}
<RenderCount />
</li>
);
});
const TodoLayout = observer(({ store }) => {
const onNewTodo = () => {
let tmp = (Math.random() * 100).toFixed(0);
console.log(tmp, tmp % 2);
if (tmp % 2 == 0) {
store.addTodo(prompt("输入新的待办:", "请来杯咖啡"));
} else {
store.addTodoSync(prompt("输入新的待办:", "请来瓶红牛"));
}
};
const fn = useCallback((id, info) => store.modify(id, info), []);
// const fn = (id, info) => store.modify(id, info);
return (
<div>
{store.report}
<ul>
{store.todos.map((todo, id) => (
<TodoItem key={id} modify={fn} id={id} todo={todo} />
))}
</ul>
{store.pendingRequests > 0 ? <marquee>正在加载……</marquee> : null}
<button onClick={onNewTodo}>新待办</button>
<small>(双击待办进行编辑)</small>
</div>
);
});
const App = () => {
return (
<>
<TodoLayout store={store}></TodoLayout>
</>
);
};
export default App;这里调用了RenderCount组件,参考前文:
这部分代码相比较官方代码,也做了一些修改。这里想说的是:react循环一个子组件的时候,添加的key是无法被子组件的props所获取的。

高阶组件 observer()
这里带来了一个新的概念,高阶组件observer(),它来自mobx-react 或者 mobx-react-lite。
使用方式是:通过observer()包裹一个React组件。用于将React组件连接到MobX状态树,将React组件转化为响应式组件。
调用方式如下:
import { observer } from "mobx-react";因此,需要安装两个第三方库了。
npm install mobx mobx-react --save或者
npm install mobx mobx-react-lite --save在mobx-react和mobx-react-lite中都有observer,后者的observer不支持将类组件转换成响应式组件,但是拥有更小的体积。
结语
更多mobx相关文章,可以参考苏南大叔的博客: