Mobx 简单可扩展的状态管理
目录
10.1 通过和Redux比较对Mobx介绍 2
10.1 通过和Redux比较对Mobx介绍
②mobx的单向数据流:
3.装饰器语法
为了引入装饰器语法,之后我们在创建项目的时候,都用此命令创建:
create-react-app 项目名 --scripts-version custom-react-scripts
我们这里用到的语法修饰器就是在’mobx’、’mobx-react’等其他一些包提供的API或组件前加上@,使其具有语法修饰器的功能。
常用的语法修饰器有: observable、@observable、@computed(get+方法名)、observer、@observer、action、@action、@inject
具体的使用方法在10.2节分类进行讲解
10.2 Mobx包含的主要概念
①普通对象(Plain Object)
普通对象指原型不存在或原型是Object.prototype的对象,可以对比后面的非普通概念进行理解。Mobx根据普通对象创建一个可观测的新对象,新对象的属性和普通对象相同,但每一个都是可观测的
import { observable,autorun } from “mobx”;
let person = observable({ //此处使用observable将属性定义为可观测
name: “Jack”,
age: 20
});
// mobx.autorun会创建一个reaction自动响应的state的变化,后面会讲到
autorun(()=>
console.log(‘name: p e r s o n . n a m e , a g e : {person.name},age: person.name,age:{person.age}’)
);
person.name = “Tom”; //输出:name:Tom,age:20
person.age = 25; //输出: name:Tom,age:20
我们用语法修饰器修饰过后的@observable来改写上面的代码
import { observable,autorun } from “mobx”;
class Person {
@observable name = “Jack”;
@observable age = 20;
}
const person = new Person();
autorun(()=>
console.log(‘name: p e r s o n . n a m e , a g e : {person.name},age: person.name,age:{person.age}’)
);
person.name = “Tom”; //输出:name:Tom,age:20
person.age = 25; //输出: name:Tom,age:20
注意几个问题:
a.只有当前普通对象已经存在的属性才会转换为可观测的,后面添加的新属性都不会自动变为可观测的。
b.属性的getter会自动转换成computed value,效果和使用@computed相同,这里我们同意使用@computed get+方法名。
c.observable会递归地便利整个对象,每当遇到对象的属性值还是一个对象时(不包含非普通对象),这个属性值将会继续被observable转换
②ES 6 Map
返回一个新的可观测的Map对象。Map对象的每一个对象都是可观测的,而且向Map
对象中添加或删除新元素的行为也是可观测的,这是Map类型的可观测state最大的特点。
import { observable,autorun } from “mobx”;
// Map可以接收一个数组作为参数,数组的每一个元素代表Map对象中的一个键值对
const map = new Map([[“name”,“Jack”],[“age”,20]]);
const person = observable(map); //person是一个可观测的Map对象
autorun(()=>
console.log(‘name: p e r s o n . g e t ( " n a m e " ) , a g e : {person.get("name")},age: person.get("name"),age:{person.get(“age”),address:${person.get(“address”)}’)
);
person.set(“address”,“Shanghai”); // 输出:name:Jack,age:20,address:Shanghai
③数组
返回一个新的可观测数组。数组元素的增加或减少都会被自动观测。
const todos = observable([“Learn React”,“Learn Redux”]);
把数组todos定义为可观测的数组(省略autorun)
todos.push(“Learn Mobx”);//在数组末尾添加一个或多个元素,并返回数组新长度
todos.shift();//把数组第一个元素删除,并返回数组新长度
④非普通对象
非普通对象特指以自定义函数作为构造函数创建的对象。observable会返回一个特殊的boxed values类型的可观测对象。返回的boxed values对象并不会把非普通对象的属性转换成可观测的,而是保存一个指向原对象的引用,这个引用是可观测的。对原对象的访问和修改需要通过新对象的get()和set()方法操作。(我们同一用装饰器语法,这里就不再写出例子)
将非普通对象的属性转换成可观测的是自定义构造函数的责任。使用装饰器@observable的方式:
import { observable,autorun } from “mobx”;
class Person {
@observable name;
@observable age;
constructor(name,age){
this.name = name;
this.age =age
}
}
var person = new Person(“Jack”,20);
autorun(()=>console.log(‘name: p e r s o n . n a m e , a g e : {person.name},age: person.name,age:{person.age}’));
person.age = 25;
⑤基本数据类型
Mobx是将包含值的属性(引用)转换成可观测的,而不是直接把值转换成可观测的,当observable接收的参数是JavaScript的基本数据类型时,Mobx不会把它们转换成可观测的,而是同处理非普通对象一样,返回一个boxed values类型的对象。
2.computed valued
computed valued是根据state衍生出来的新值,新值必须是通过纯函数计算得到的。
当computed value有被reaction使用时,computed value才会自动更新。
一般通过computed和@computed创建computed value
举一个@computed的例子:
import { observable,computed,autorun } from ‘mobx’;
class Person {
@observable name;
@observable age;
@computed get isYoung(){ //使用@computed装饰器创建computed value
return this.age < 25;
}
constructor(name,age){
this.name = name;
this.age = age
}
}
const person = new Person(“Jack”,20);
//使用autorun创建reaction响应,这里用到了isYoung所以@computed自动更新
autorun(()=>console.log(‘name: p e r s o n . n a m e , i s Y o u n g : {person.name},isYoung: person.name,isYoung:{person.isYoung}’ ));
person.age = 25; //输出:name:Jack,isYoung:false
3.reaction
除了observer/@observer外,常用的创建reaction的API有autorun、reaction、when,这几个API直接作用与函数而不是组件。
Observer/@observer是mobx-react这个包提供的API,常用的使用方式有三种:
observable((props,context)=>ReactElement)
observable(class MyComponent extends React.Component{…})
@observable class MyComponent extends React.Component{…}
简单的说,reaction产生的不是一个值,而是执行一些有副作用的动作,例如打印信息到控制台(前面用到的autorun都是console.log)、发送网络请求、根据React组件树更新DOM(本章给的源代码中使用@observer将render组件封装成reaction,使用observer将要渲染的TodoView封装成reation)等
①autorun
autorun在前面已多次使用,这里就不再多做介绍,注意一点:autorun会返回一个清除函数disposer,当不需要观察相关state的变化时,可以调用disposer函数清除副作用
disposer();//清除autorun
②reaction
用法:reaction( () => data , data => { sideEffect } , option?)
它接收两个参数,第一个函数返回被观测的state,这个返回值同时是第二个函数的输入值,只有第一个函数的返回值发生变化时,第二个函数才会执行,第三个参数option是可选参数,一般很少用到,用到时我们在研究该怎么使用。注意:reaction也会返回一个清除函数disposer。
课堂上我们还遗留一个问题就是reaction1和reaction2的输出顺序,看了一下mobx官网提供的例子,可以认为本书作者先输出reaction2再输出reaction1就是先展示了正确的使用方法,再展示了错误的使用方法
③when
用法:when( () = > condition, () =>{ sideEffect })
condition会自动响应它使用的任何state变化,当condition返回true时,函数sideEffect会执行一次,且只执行一次。
④三种方式使用的场景
当你想创建一个响应式函数,而该函数本身永远不会有观察者时,可以使用mobx.autorun
相比于reaction可以对跟踪哪些对象有更多的控制。
而when非常适合用在以响应式的方式执行取消或清除逻辑的场景。
4.action
action是用来改变state的函数。Mobx提供了API action和@action用来包装action函数
action( fn )
@action classMethod
当函数内多次修改state时,action/@action会执行批处理操作,只有所有的修改都执行完成后,才会通知相关的computed value和reaction
使用action要注意函数内this指向的问题
class Ticker {
@observable tick = 0;
@action
increment(){
this.tick++;//这里的this并不是期待的Ticker,而是指向全局的window对象
}
}
可以使箭头函数解决this绑定的问题:
class Ticker {
@observable tick = 0;
@action
increment=()=>{ //箭头函数
this.tick++;
}
}
如果你不想使用箭头函数的话,还可以使用Mobx提供的@action.bound和action.bound两个API帮助完成this绑定的工作:
@action.bound
increment(){
this.tick++;
}
10.3 Mobx响应的常见误区
① Mobx是通过追踪属性的访问来追踪值的变化,而不是直接追踪值本身的变化
必须在Mobx的computed value和reaction中解引用可观测对象的属性,才能正确观测到这些属性值的变化。
②Mobx只追踪同步执行过程中的数据。
异步执行(有时间延时)autorun是不会有响应的
③observer创建的组件,只有在当前组件render方法中直接使用的数据才会被追踪
const MyComponent = observer(({ todo }) =>
在解释一下inject
我们这里先构造store及其初始值
const todos = observable([]);
todos.push({ id:1,title:“Task1” });
todos.push({ id:2,title:“Task2” });
然后在MyComponent外部先用@observer封装成reaction,然后再使用@inject(“store”);
inject从context中取出store对象,注入到组件的props中。