mobx 是一个简单可扩展的状态管理库
mobx 学习成本更低(对比 redux 要学 16 小时,mobx 只需学两小时就能上手做项目了),性能更好的状态解决方案
总之,mobx:

状态变化引起的副作用(如 UI 更新)应该被自动触发


React 利用虚拟 DOM 来优化 UI 的渲染,以减少 DOM 操作成本。 MobX 则提供了优化将应用状态同步到 React 组件内的机制,通过使用了一种响应式的状态依赖图,该依赖图严格只在需要的时候更新,并且不会出现代码腐化。

mkdir xxx
cd xxx
yarn init -y
yarn add webpack webpack-cli babel-core babel-loader babel-preset-env babel-preset-react babel-preset-stage-0 babel-plugin-transform-decorators-legacy react mobx mobx-react
需要安装
react

{
"scripts": {
"start": "webpack --mode development -w"
}
}
src/index.jsyarn start -> 在根目录下出现一个build目录
index.html(写个<div id="root"></div>就行了),引入打包生成的bundle.jslive server 功能打开这个index.html视频是 18 年 3 月份的,用了 webpack5 后,安装的依赖包得改了! -> webpack 配置也要改 -> 我没有配置
stage-0,似乎 babel7 后,这个配置就取消了! -> 这个配置仅仅是打包 JS 文件
prototype对象操作读修饰器还是装饰器,都随你
装饰器的设计理念:

在不改造 托尼·史塔克(Tony Stark) 本体的前提下,通过加装盔甲、飞行器的方式增强 Tony 的能力,从而“变成”钢铁侠。 -> 组合大于继承
例子:

原理:
// 修改了 Person 这个类的行为,增加了惊静态属性 isTestable
function testable(target) {
target.isTestable = true
}
// @testable
class Person {
}
// 就是调用了函数
testable(Person)
console.log(Person.isTestable) // true
需求:给一个叫Circle类的东西添加一个实例属性PI,要求这个属性「只读的」,毕竟PI是恒定的!
// target:目标的原型,即函数的原型 -> key:类的属性 -> descriptor:描述器
function readonly(target,key,descriptor) {
console.log(target,key)
console.log(descriptor)
descriptor.writable = false
}
class Circle {
// 这是实例的属性
@readonly PI = 3.14
computed() {
}
}
let c1 = new Circle()
// 视频里是报了 Cannot assign to read only property 'PI' of object 这样的错误
// 自己本地测试没有报错,但是起了效果
c1.PI = 3.15
console.log(c1.PI) // 3.14

💡:在 VS Code 里边,PI 会有警告?
配置 jsconfig.json :
{
"compilerOptions": {
"experimentalDecorators": true
}
}
💡:描述器(descriptor,也叫属性描述符)是什么?
Object.defineProperty(obj, prop, descriptor)
我们可以这样给一个对象定义属性:
let obj = {}
obj.name = 'xxx'
也可以这样:
Object.defineProperty(obj,'age',{
value: 666, // 实际的值
enumerable: false, // 是否可枚举 -> 可以用 for in 循环吗?
writable: true, // 是否可修改
configurable: false // 是否可配置 -> 可以删除这个属性配置吗? -> delete obj.age
})
console.log(obj.age) // 666
obj.age = 777
console.log(obj.age) // 777
for (const key in obj) {
console.log(key) // 只 log 了一个 name 属性
}
delete obj.age
console.log(obj.age) // 777
第一种姿势其实就是第二种姿势的简写,只是它默认就是可写的、可枚举的,以及可配置的!
题外话,其实,属性描述符descriptor总共分为两种形式:
注意
我们上边那种形式就是把descriptor当作是「数据描述符」来表现:
const res = Object.getOwnPropertyDescriptor(obj,'age');
console.log(res) // { value: 777, writable: true, enumerable: false, configurable: false }
而「访问器描述符」(也可以叫存取描述符)的形式就是在一个的对象里边用get/set
注意 -> 描述符必须是两种形式之一,不能同时是两者

修饰器不仅可以修饰类,还可以修饰类的属性和方法
例子:打印函数调用日志
class Calculator {
@logger
add(a,b) {
return a+b
}
}
function logger(target,name,descriptor) {
let oldVal = descriptor.value
descriptor.value = function() {
console.log(`${name}(${Array.from(arguments).join(',')})`)
return oldVal.apply(this,arguments)
}
}
let c = new Calculator()
let res = c.add(1,2)
console.log(res) // add(1,2) -> 3
原理:

let oldDescriptor = Object.getOwnPropertyDescriptor(Calculator.prototype,'add')
logger(Calculator.prototype,'add',oldDescriptor)
Object.defineProperty(Calculator.prototype,'add',oldDescriptor)
💡:为啥我定义在实例属性上,这target居然也是原型对象呢?按理说不应该是实例吗?

也许,官方约定了,传的就是原型属性,而不是实例对象!
话说,如果add是个实例方法,而且它是个箭头函数,这又会怎么样呢?
如:
class Calculator {
@logger
add = (a, b) => {
return a + b;
};
// @test
name = "c";
}
我测试了一下,descriptor.value的值是undefined,你得透过descriptor.initializer()来获取add方法,最终代码:
function logger(target, name, descriptor) {
console.log(target, name);
console.log(descriptor);
console.log(descriptor.initializer());
// let oldVal = descriptor.value;
// console.log(oldVal); // undefined
let oldVal = descriptor.initializer();
function _oldVal() {
console.log(`${name}(${Array.from(arguments).join(',')})`)
return oldVal.apply(this,arguments)
}
descriptor.initializer = function initializer() {
return _oldVal
}
}
效果:

descriptorget方法用于拦截某个属性的读取操作,可以接受三个参数,依次为「目标对象」、「属性名」和「Proxy实例本身」set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为「目标对象」、「属性名」、「属性值」和「Proxy实例本身」let proxy = new Proxy(target,handler)
let p1 = new Proxy({name:'xxx'},{
get(target,key,receiver) {
console.log(`getting ${key}`)
console.log(receiver)
return Reflect.get(target,key,receiver)
},
set(t,k,value,r) {
console.log(`setting ${key}`)
return Reflect.set(t,k,value,r)
}
})
console.log(p1.name)
// getting name
// { name: 'xxx' }
// xxx
receiver就是p1-> 这是个没啥用的参数
💡:Reflect?
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。Reflect 不是一个函数对象,因此它是不可构造的。Reflect 的所有的方法都是静态的就和 Math 一样,目前它还没有静态属性。
Reflect 对象的方法与 Proxy 对象的方法相同。
Reflect 一共有 13 个静态方法,它可以分为:
Object 上的方法,将它转义到了 Reflect 上,并作了小改动,让方法更加合理,如defineProperty、getOwnPropertyDescriptorin -> has(target, key)、delete -> deleteProperty(target, key)为什么需要Reflect这个东西?(产生的背景和必要性)
因为 proxy 代理不了一些 Map、Set 之类的属性,但是 Proxy 为它们提供了默认的插槽 这时候 Reflect 就能派上用场了
例子:
Reflect.get ( target, propertyKey [ , receiver ]) -> 该方法用来获取对象中某个属性,如:
const testObject = {
a: 'you',
b: 'like'
}
Reflect.get(testObject, 'a') === 'you' // true
Reflect.get(testObject, 'b') === 'like' // true
Reflect.set ( target, propertyKey, V [ , receiver ] ) -> 该方法用来设置对象中某个属性,如:
const testObject = {
a: 'you',
b: 'like'
}
Reflect.set(testObject, 'c', 'javascript') // true
Reflect.get(testObject, 'c') === 'javascript' // true
总之,上边的Proxy返回一个return Reflect.set(t,k,value,r),意味着不使用赋值语句了!
💡:老师的日常笔记目录?

observable就是一种让数据的变化可以被观察的方法对象、数组
import { observable } from "mobx";
let o1 = observable({ name: "frank" });
console.log(o1.name);
let arr1 = observable([1, 2, 3]);
console.log(arr1);
arr1.pop();
arr1.push(4);
arr1.unshift(0);
console.log(arr1);

observable返回时一个Proxy实例
需求:改变数据,执行callback
使用observe(动词,观察):

字符串、布尔值、数字、Symbol(独一无二的值)

对于存有基本类型值的属性,不需要写成@observable.box这样,直接@observable age = 18这样就好了,observable内部自己帮我们封装了!
Mobx6 之前的做法:
import { observable, observe } from "mobx";
class Person {
@observable name = "frank";
@observable age = 18;
@observable isMarried = false;
@observable hobby = ["敲代码", "打 LOL", "睡觉"];
@observable home = { name: "海岛" };
@observable skills = new Map();
}
let p = new Person()
observe(p,(c)=>{
console.log(c)
})
console.log(p)
p.age = 19
p.name = 'xxx'
p.hobby.push('玩耍')
p.home.name = '椰岛'
Mobx6 不要 decorator 了:
import { makeAutoObservable,observe } from "mobx"
class Person {
name = "frank";
age = 18;
isMarried = false;
hobby = ["敲代码", "打 LOL", "睡觉"];
home = { name: "海岛", number: '101'};
skills = new Map();
constructor() {
makeAutoObservable(this)
}
}
let p = new Person()
// p 实例旗下的属性改变了,都会执行这个 callback
observe(p,(c)=>{
console.log(c)
})
// p 实例的 name 属性改变了,就会执行这个 callback
observe(p,'name',(c)=>{
console.log(c)
})
console.log(p) // 实例的自有属性都被 get、set 了,即都被监听了
p.age = 19
p.name = 'xxx'
// p.hobby.push('玩耍')
// p.home.name = '椰岛'
// 遵守数据不可变理念
p.hobby = [...p.hobby,'玩耍']
p.home = {...p.home,name:'椰岛'}
console.log(p.hobby[3]) // 玩耍
console.log(p.home.name) // 椰岛
console.log(p.home.number) // 101

在配合 React 使用时,
observe这个操作对应的是mobx-react里边的observer
再看那张图:

➹:Mobx 真的好用吗?它有什么优缺点?主要适用于什么场景? - 知乎
💡:如果我非得要在 Mobx6 里边使用装饰器模式呢?
那么你得这样做:
import { makeObservable,observable, observe } from "mobx";
class Person {
@observable name = "frank";
@observable age = 18;
@observable isMarried = false;
@observable hobby = ["敲代码", "打 LOL", "睡觉"];
@observable home = { name: "海岛" };
@observable skills = new Map();
constructor() {
makeObservable(this)
}
}
let p = new Person()
observe(p,(c)=>{
console.log(c)
})
console.log(p)
p.age = 19
p.name = 'xxx'
即添加makeObservable就行了!
但这看起来很没有必要,也许这是为了兼容老版本所提供的一个开关按钮吧!
如果你有一个属性是算出来的,那你就用
computed呗!
decorator 使用.get() 来获取计算的当前值.observe(callback) 来观察值的改变。computed 值可以引用其它 computed 的值,但是不能循环引用示例:

其实,我把@computed去掉,结果也是一样的! -> phone是个访问器属性啊!
难道只是为了修饰它是一个计算属性吗?
💡:如何监听计算属性的变化,然后做点什么呢?

效果:

注意:如果你用了@observable修饰访问器属性,那么会报'observable' cannot be used on getter/setter properties的错误
💡:把computed作为函数使用?

💡:autorun也可以起到observe的作用?

你改成这样:
autorun((c)=>{
console.log(c) // Reaction 实例
console.log(p.age)
})
那么p.age改变了,就会autorun一下了!
视频里说用
autorun的原因:不能p.phone.observe()这样 ->p.phone返回的就是一个字符串了,所以用autorun就能解决这个问题了!
话说,observe和autorun差别大吗?
autorun(callback)可以很方便地就实现了状态变化,重新渲染 UI! -> 监听callback里用到的状态,状态一旦变化,就会重新render-> 或许是mobx-react里的observer实现原理
observe方法了 -> 这里指的p.phone.observe姿势!mobx.autorunautorun 时,所提供的函数总是立即被触发一次,然后每次它的依赖关系改变时会再次被触发import { autorun, computed, makeObservable, observable } from "mobx";
class Store {
constructor() {
makeObservable(this);
}
@observable province = "广东";
@observable city = "广州";
@computed get home() {
return this.province + this.city;
}
}
let store = new Store();
autorun(() => {
//console.log(store.province,store.city);
console.log(store.home); // 执行了三次,广东广州,湖南广州,湖南长沙
});
store.province = "湖南";
store.city = "长沙";
注意:计算属性home依赖的本质是store.province和store.city!我把上边那个注释给取消掉了,也是执行了三次!
为什么会有它? -> 有时候需要等到某个时间点再打印! -> 有点像事件的
once
predicate,直到返回 true。true,给定的 effect 就会被执行,然后 autorunner(自动运行程序) 会被清理。 -> 只执行一次语法:
when(predicate: () => boolean, effect?: () => void, options?)
示例:
import { makeObservable, observable, when } from "mobx";
class Person {
@observable age = 18;
constructor(age) {
makeObservable(this);
this.age = age;
}
}
let boy = new Person(19);
let girl = new Person(18);
// when 会等待条件满足,一旦满足就会执行回调并销毁监听
// callback 执行一次后就不会在执行了,因为监听被销毁了!
when(
() => {
return boy.age >= 22 && girl.age >= 20;
},
() => {
console.log(`男${boy.age}`, `女${girl.age}`);
console.log("我们去领结婚证吧!");
}
);
setInterval(() => {
boy.age++;
girl.age++;
}, 1000);
主动取消监听:
// when 函数会返回一个取消监听的函数,如果你调用它,那就直接取消监听了!
// 相当于,你 add 了,紧接着你又 remove 了,等于白干!
let disposer = when(
() => {
return boy.age >= 22 && girl.age >= 20;
},
() => {
console.log(`男${boy.age}`, `女${girl.age}`); // 22 20
console.log("我们去领结婚证吧!"); // ...
}
);
disposer()
最重要的一个
autorun的变种,autorun会自动触发,reaction对于如何追踪observable赋予了更细粒度的控制autorun的是,当创建时「效果函数」不会直接运行,只有在数据表达式首次返回一个新值后才会运行示例:
let boy = new Person(19);
let girl = new Person(18);
// arr -> [20,19]
reaction(
() => [boy.age, girl.age],
(arr) => console.log(arr)
);
setInterval(() => {
boy.age++;
girl.age++;
}, 1000);
注意:
boy的age默认值是19,你boy.age = 19是不会触发callback执行的!callback执行 -> 这有点像useEffect啊!when和reaction同时存在,都满足条件触发了 -> 谁先写,那就先触发谁!💡:reaction与autorun的区别?
autorun:不改变数据就触发一次callbackreaction:不改变数据就不触发想要批量操作,而不是一个状态变化了,就会触发执行
callback-> 全部改完再触发!
autorun和reaction执行import { action, autorun, computed, makeObservable, observable } from "mobx";
class Person {
@observable area = "+86";
@observable number = "12345678911";
@computed get phone() {
return this.area + "-" + this.number;
}
constructor() {
makeObservable(this);
}
@action switchPhone(area, number) {
this.area = area;
this.number = number;
}
}
let p1 = new Person();
autorun(() => {
console.log(p1.phone);
});
p1.switchPhone("+1", "11122233366");
// 默认执行:+86-12345678911
// 改了两个状态才执行一次:+1-11122233366
如果我们想这样做:
let xxx = p1.switchPhone
xxx('+1',"11122233366")
那么xxx里边的this显然不是p1实例了
所以我们就有了@action.bound -> 它帮我们绑定了switchPhone里边的this为p1实例
import { action, autorun, computed, makeObservable, observable } from "mobx";
class Person {
@observable area = "+86";
@observable number = "12345678911";
@computed get phone() {
return this.area + "-" + this.number;
}
constructor() {
makeObservable(this);
}
@action.bound switchPhone(area, number) {
this.area = area;
this.number = number;
}
}
let p1 = new Person();
autorun(() => {
console.log(p1.phone);
});
// p1.switchPhone("+1", "11122233366");
let xxx = p1.switchPhone
xxx('+1',"11122233366")
@action.bound把switchPhone搞成为switchPhone.bind(this)了!
为啥需要? -> 随意组合其它需要被修改的状态,而不是当你需要修改
phone这个计算属性时,需要定义一个用@action修饰的switchPhone方法,然后修改area和number状态!
它像是一个事务 -> 用来批处理
示例:
import { observable, runInAction } from "mobx"
const state = observable({ value: 0 })
runInAction(() => {
state.value++
})
console.log(state.value) // 2
runInAction(()=>{
p1.area = '+1';
p1.number = '11122233366';
})
➹:关于 mobx runInAction 的使用 - 知乎
render 方法包装为 autorunobserver 修饰 -> 按需渲染yarn add react react-dom mobx-react
mobox-react -> react 结合 mobx 使用,类似 react-redux
组件直接读:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { observable, action, makeObservable } from "mobx";
import { observer } from "mobx-react";
class Store {
@observable number = 0;
@action.bound add() {
this.number++;
}
constructor() {
makeObservable(this)
}
}
let store = new Store();
// Counter 类组件用到了可观察属性 number,所以需要用 @observer 修饰它
// @observe 即起到了 autorun 的作用,第一次默认渲染,之后根据组件所依赖的可观察状态是否变化来渲染
@observer
class Counter extends Component {
render() {
return (
<div>
<p>{store.number}</p>
<button onClick={store.add}>+1</button>
</div>
);
}
}
ReactDOM.render(<Counter />, document.querySelector("#root"));

另一种获取数据的做法(传过来):
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { observable, action, makeObservable } from "mobx";
import { observer } from "mobx-react";
class Store {
@observable counter = { number: 0 };
@action.bound add() {
this.counter.number++;
}
constructor() {
makeObservable(this)
}
}
let store = new Store();
@observer
class Counter extends Component {
// let store = this.props.store
render() {
return (
<div>
<p>{this.props.counter.number}</p>
<button onClick={this.props.add}>+1</button>
</div>
);
}
}
ReactDOM.render(
<Counter counter={store.counter} add={store.add} />,
document.querySelector("#root")
);
💡:关于@action.bound的实现?
不是那么简单的实现,我搞不明白的是:
action.bound这个函数,在我们还未new Store()的时候,就已经执行了,可它是如何确定add里边的this是未来的实例store呢?
如果我们不加.bound,我们是直接这样了:
let store = new Store();
store.add = store.add.bind(store)
➹:autobind-decorator/index.js at master · andreypopp/autobind-decorator
import React, { Component, Fragment } from "react";
import ReactDOM from "react-dom";
import { observable, action, computed, makeObservable } from "mobx";
import PropTypes from "prop-types";
import { observer, PropTypes as ObservablePropTypes } from "mobx-react";
class Todo {
id = Math.random();
@observable text = "";
@observable completed = false;
constructor(text) {
makeObservable(this)
this.text = text;
}
@action.bound toggle() {
this.completed = !this.completed;
}
}
class Store {
constructor() {
makeObservable(this)
}
@observable todos = [];
@computed get left() {
return this.todos.filter((todo) => !todo.completed).length;
}
@computed get filterTodos() {
return this.todos.filter((todo) => {
switch (this.filter) {
case "completed":
return todo.completed;
case "uncompleted":
return !todo.completed;
default:
return true;
}
});
}
@observable filter = "all";
@action.bound changeFilter(filter) {
this.filter = filter;
console.log(this.filter);
}
@action.bound addTodo(text) {
this.todos.push(new Todo(text));
}
@action.bound removeTodo(todo) {
this.todos.remove(todo);
}
}
@observer
class TodoItem extends Component {
static porpTypes = {
todo: PropTypes.shape({
id: PropTypes.number.isRequired,
text: PropTypes.string.isRequired,
completed: PropTypes.bool.isRequired,
}).isRequired,
};
render() {
let { todo } = this.props;
return (
<Fragment>
<input
type="checkbox"
onChange={todo.toggle}
checked={todo.completed}
/>
<span className={todo.completed ? "completed" : ""}>{todo.text}</span>
</Fragment>
);
}
}
@observer
class TodoList extends Component {
static propsTypes = {
store: PropTypes.shape({
addTodo: PropTypes.func,
todos: ObservablePropTypes.observableArrayOf(
ObservablePropTypes.observableObject
),
}).isRequired,
};
state = { text: "" };
handleSubmit = (event) => {
event.preventDefault();
this.props.store.addTodo(this.state.text);
this.setState({ text: "" });
};
handleChange = (event) => {
this.setState({ text: event.target.value });
};
render() {
let {
filterTodos,
left,
removeTodo,
filter,
changeFilter,
} = this.props.store;
return (
<div className="todo-list">
<form onSubmit={this.handleSubmit}>
<input
placeholder="请输入待办事项"
type="text"
value={this.state.text}
onChange={this.handleChange}
/>
</form>
<ul>
{filterTodos.map((todo) => (
<li key={todo.id}>
<TodoItem todo={todo} />
<button onClick={() => removeTodo(todo)}>X</button>
</li>
))}
</ul>
<p>
<span>你还有{left}件待办事项!</span>
<button
onClick={() => changeFilter("all")}
className={filter === "all" ? "active" : ""}
>
全部
</button>
<button
onClick={() => changeFilter("uncompleted")}
className={filter === "uncompleted" ? "active" : ""}
>
未完成
</button>
<button
onClick={() => changeFilter("completed")}
className={filter === "completed" ? "active" : ""}
>
已完成
</button>
</p>
</div>
);
}
}
let store = new Store();
ReactDOM.render(<TodoList store={store} />, document.querySelector("#root"));

只监听数组元素的添加或删除:
import { observable, makeObservable, observe } from "mobx";
class Store {
@observable todos = []
constructor() {
makeObservable(this)
observe(this.todos,e=>{
console.log(e)
})
}
}
let store = new Store()
store.todos.push('吃饭饭')
store.todos.push('睡觉觉')
console.log(store)
console.log(store.todos)

当我这样做时:
let store = new Store()
store.todos.push('吃饭饭')
store.todos.push(['睡觉觉'])
store.todos.push({name:'frank',age:18})
// 视频里需要 store.todos.get(1) 才能拿到数组元素,而现在我用的这个最新版是不用的!
console.log(store.todos[1])
store.todos[1].push('打豆豆')
console.log(store.todos[2].name)

如果我们非得让它可以触发执行呢?那么你得这样做:
import { observable, makeObservable, observe } from "mobx";
class Store {
@observable todos = [];
// 里边存放着所有的取消监听的函数,只要调用了,监听结束,即数据变化了,不会再执行 cb 了
disposers = [];
constructor() {
makeObservable(this);
observe(this.todos, (event) => {
console.log(event);
// 让以前的所有「取消监听函数」执行
this.disposers.forEach((disposer) => disposer());
this.disposers = [];
for (let todo of event.object) {
let disposer = observe(todo, (e) => {
console.log(e);
});
this.disposers.push(disposer);
}
});
}
}
let store = new Store();
// 不加 observable 会说没有权限,其实一般是不用加才对的!
store.todos.push(observable("吃饭饭"));
store.todos.push(["睡觉觉"]);
store.todos.push({ name: "frank", age: 18 });
// 视频里需要 store.todos.get(1) 才能拿到数组元素,而现在我用的这个最新版是不用的!
console.log(store.todos[1]);
store.todos[1].push("打豆豆");
console.log(store.todos[2].name = 'Jack');
store.todos[0].set('吃菜菜')
console.log(store.todos)
间谍
这个调试可能会用,但开发就不会用了!因为性能很差 -> 它会监听所有变化!
import { observable, makeObservable, spy } from "mobx";
spy((e) => console.log(e));
class Store {
@observable todos = [];
constructor() {
makeObservable(this);
}
}
let store = new Store();
store.todos.push("吃饭饭");
store.todos.push(["睡觉觉"]);
store.todos.push({ name: "frank", age: 18 });
// 视频里需要 store.todos.get(1) 才能拿到数组元素,而现在我用的这个最新版是不用的!
// 为啥要这样?因为视频里用 push(observable({name:'xxx',age:18})) 这样样 push 一个数据的
// 而这个元素显然不是一个 普通对象,而是一个可被观察的普通对象
console.log(store.todos[1]);
store.todos[1].push("打豆豆");
console.log((store.todos[2].name = "Jack"));
store.todos[0] = "吃菜菜";
console.log(store.todos);

递归地将一个 (observable) 对象转换为 javascript 结构。 支持 observable 数组、对象、映射和原始类型。 计算值和其它不可枚举的属性不会成为结果的一部分。默认情况下可以正确支持检测到的循环,但也可以禁用它来获得性能上的提升。
语法:toJS(value, options?)
示例 1:
import {
observable,
toJS,
isObservableObject,
} from "mobx";
var obj = observable({
x: 1,
});
console.log(obj);
var clone = toJS(obj);
console.log(clone);
console.log(isObservableObject(obj)); // true
console.log(isObservableObject(clone)); // false

示例 2:
import {
observable,
toJS,
isObservableObject,
makeObservable,
observe,
} from "mobx";
class ToDo {
@observable todos = [];
disposers = [];
constructor() {
makeObservable(this);
observe(this.todos, (change) => {
console.log(change);
this.disposers.forEach((disposer) => disposer());
this.disposers = [];
for (let todo of change.object) {
this.disposers.push(
observe(todo, (change) => {
this.save();
//console.log(change)
})
);
}
this.save();
});
}
save() {
console.log(this.todos)
localStorage.setItem("todos", JSON.stringify(toJS(this.todos)));
}
}
let todoList = new ToDo();
todoList.todos.push(observable("吃饭"));
todoList.todos.push({ name: "frank", age: 18 });

trace 是一个小工具,它能帮助你查找为什么计算值、 reactions 或组件会重新计算
import React, { Component, Fragment } from "react";
import ReactDOM from "react-dom";
import {
trace,
observable,
action,
computed,
observe,
spy,
toJS,
makeObservable,
} from "mobx";
import PropTypes from "prop-types";
import { observer, PropTypes as ObservablePropTypes } from "mobx-react";
spy((event) => {
//console.log(event);
});
class Todo {
id = Math.random();
@observable text = "";
@observable completed = false;
constructor(text) {
makeObservable(this);
this.text = text;
}
@action.bound toggle() {
this.completed = !this.completed;
}
}
class Store {
disposers = [];
constructor() {
makeObservable(this);
observe(this.todos, (change) => {
console.log(change);
this.disposers.forEach((disposer) => disposer());
this.disposers = [];
for (let todo of change.object) {
this.disposers.push(
observe(todo, (change) => {
this.save();
//console.log(change)
})
);
}
this.save();
});
}
save() {
localStorage.setItem("todos", JSON.stringify(toJS(this.todos)));
}
@observable todos = [];
@computed get left() {
return this.todos.filter((todo) => !todo.completed).length;
}
@computed get filterTodos() {
return this.todos.filter((todo) => {
switch (this.filter) {
case "completed":
return todo.completed;
case "uncompleted":
return !todo.completed;
default:
return true;
}
});
}
@observable filter = "all";
@action.bound changeFilter(filter) {
console.log("hi");
this.filter = filter;
console.log(this.filter);
}
@action.bound addTodo(text) {
this.todos.push(new Todo(text));
}
@action.bound removeTodo(todo) {
this.todos.remove(todo);
}
}
@observer
class TodoItem extends Component {
static propTypes = {
todo: PropTypes.shape({
id: PropTypes.number.isRequired,
text: PropTypes.string.isRequired,
completed: PropTypes.bool.isRequired,
}).isRequired,
};
render() {
trace();
let { todo } = this.props;
return (
<Fragment>
<input
type="checkbox"
onChange={todo.toggle}
checked={todo.completed}
/>
<span className={todo.completed ? "completed" : ""}>{todo.text}</span>
</Fragment>
);
}
}
@observer
class TodoFooter extends Component {
static propTypes = {};
render() {
trace();
let { left, filter, changeFilter } = this.props.store;
return (
<div>
<span>你还有{left}件待办事项!</span>
<button
onClick={() => changeFilter("all")}
className={filter === "all" ? "active" : ""}
>
全部
</button>
<button
onClick={() => changeFilter("uncompleted")}
className={filter === "uncompleted" ? "active" : ""}
>
未完成
</button>
<button
onClick={() => changeFilter("completed")}
className={filter === "completed" ? "active" : ""}
>
已完成
</button>
</div>
);
}
}
@observer
class TodoViews extends Component {
render() {
let { filterTodos, removeTodo } = this.props.store;
return (
<ul>
{filterTodos.map((todo) => (
<li key={todo.id}>
<TodoItem todo={todo} />
<button onClick={() => removeTodo(todo)}>X</button>
</li>
))}
</ul>
);
}
}
@observer
class TodoHeader extends Component {
state = { text: "" };
handleSubmit = (event) => {
event.preventDefault();
this.props.store.addTodo(this.state.text);
this.setState({ text: "" });
};
handleChange = (event) => {
this.setState({ text: event.target.value });
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
placeholder="请输入待办事项"
type="text"
value={this.state.text}
onChange={this.handleChange}
/>
</form>
);
}
}
@observer
class TodoList extends Component {
static propsTypes = {
store: PropTypes.shape({
addTodo: PropTypes.func,
todos: ObservablePropTypes.observableArrayOf(
ObservablePropTypes.observableObject
),
}).isRequired,
};
render() {
trace();
return (
<div className="todo-list">
<TodoHeader store={this.props.store} />
<TodoViews store={this.props.store} />
<TodoFooter store={this.props.store} />
</div>
);
}
}
let store = new Store();
ReactDOM.render(<TodoList store={store} />, document.querySelector("#root"));

测试:读本地数据 -> 赋值给
todos-> 渲染 -> 会有 bug!