写在开始,一下均为开发过程中个人的理解,如有错误恳请斧正,后期开发过程有问题或新发现,恳请诸位及时分享。
装饰器语法
语法糖,用于给对象在运行期间动态的增加某个功能,职责等。核心上类似于高阶函数。
function log(target) { // 这个 target 在这里就是 MyClass 这个类
target.prototype.logger = () => `${target.name} 被调用`
}
// 装饰器只对后面紧跟的对象产生影响
@log
class MyClass { }
const test = new MyClass()
test.logger() // MyClass 被调用
Mobx基本概念
- State(状态)
等同于react中的state - Derivations(衍生)
分为Computed 和 Reactions;
Computed:
计算属性,对state中某个属性进行加工计算,返回加工计算后的值。
Reactions:
反应,当state中某个属性发生改变时需要发生的的副作用。 - Actions(动作)
动作,修改state
Mobx核心方法
- autoRun
这个函数非常智能,用到了什么属性,就会和这个属性挂上钩,从此一旦这个属性发生了改变,就会触发回调,通知你可以拿到新值了。没有用到的属性,无论你怎么修改,它都不会触发回调。
在react中的应用:mobx使用autoRun包裹了我们的组件,使这个组件变成受控组件,并且将追踪render生命周期方法内的所有state。
Mobx基础用法
- 定义一个可追踪state变量
import {observable} from mobx
class test {
@observable testStr = '123'
}
- 定义一个action修改state变量
import {observable,action} from mobx
class test {
@observable testStr = '123'
@action setTestStr(param){
this.testStr = param
}
}
action和setState的差别:action是同步的,所以如果你有两个连续的action修改变量,则会刷新两次组件。可以选择合并action。如果一个action中修改多个变量,且存在某个变量是异步修改,则可以考虑使用Transaction替代action。Transaction是mobx底层API,有兴趣的同学自行研究。
- 获取一个计算值
import {observable,action} from mobx
class test {
@observable testStr1 = 123
@observable testStr2 = 456
@computed get testStr(){
retutn testStr1 + testStr2
}
// 每当testStr1和testStr2 发生改变时,testStr也将发生改变。
}
Mobx对什么做出响应
文档中写到:MobX 追踪属性访问,而不是值。
说的通俗点就是Mobx追踪的是变量在堆栈中的地址变化,而不是地址保存的值。
- 基础数据类型
import {observable,action} from mobx
class test {
@observable testNum = 123
@observable testStr = 'dt'
@observable testbool = true
@action setNum (param){
// this.testNum = 123 // 不响应
// this.testNum = this.testNum // 不会响应
this.testNum = param // param变则响应
}
@action setStr (param){
// this.testStr = '123' // 第一次响应
// this.testStr = this.testStr // 不响应
this.testStr = param // param变则响应
}
@action setBool (param){
// this.testbool = false // 第一次响应
// this.testbool = this.testNum // 不响应
this.testbool = param // param变则响应
}
}
- 对象类型
import {observable,action} from mobx
class test {
@observable testObj = {
parent:{
child: 'hi,I`m children'
}
}
@observable testArr = [123,456]
@action setArr (param){
// this.testObj = {
// parent:{
// child: 'hi,I`m children'
// }
// }
// 此时不论引用的是testObj还是其中的属性都将做出响应。
// this.testObj = this.testObj // 不响应。
// this.testObj = param // 每次一都响应,无论是否变化。
// this.testObj.parent = {child: 'hi,I`m children'}
// 此时如果引用testObj则不响应,引用testObj.parent和testObj.parent.child都将响应。
// this.testObj.other = '123'
// 引用testObj,estObj.parent和testObj.parent.child都将不响应。
}
@action setChild(param){
// this.testObj.parent.child = param //引用this.testObj.parent.child, param变则响应
// this.testObj.parent = {child: 'hi,I`m children'}//引用this.testObj.parent.child, 次次响应
this.test.other = {child:'hi,I`m other'} //引用this.testObj.parent.child, 不响应
}
@action setArr(param){
// this.testArr.push(789) // 引用this.testArr,不响应,引用this.testArr.length次次响应。引用this.testArr[n],n小于length响应,n大于length不响应
// this.testArr = [123,456] // 次次响应
this.testArr = [].concat(this.testArr) // 次次响应
}
}
数组:数组在mobx中有别于其他类型,对数组进行push,如果直接在autorun中引用这个数组,并不会响应,但是如果引用数组的方法或者数组范围内的某个值都将触发响应。<div>{this.props.store.arr}<div>这种写法也会触发响应,其原理类似应用了数组的map方法。
Mobx处理异步或者并发
- promise
错误做法
class Store {
@observable githubProjects = []
@observable state = "pending" // "pending" / "done" / "error"
@action
fetchProjects() {
this.githubProjects = []
this.state = "pending"
fetchGithubProjectsSomehow().then(
projects => {
const filteredProjects = somePreprocessing(projects)
this.githubProjects = filteredProjects
this.state = "done"
},
error => {
this.state = "error"
}
)
}
}
以上操作的将会抛出异常,原因为fetchGithubProjectsSomehow的异步回调函数不属于fetchProjects动作的一部分,简单的说就是this指向不对。最简单的修改方式就是再定义两个action,分别作为fetchGithubProjectsSomehow的两个异步回调。但是此方法需要额外定义,所以放弃采用action内联写法。
class Store {
@observable githubProjects = []
@observable state = "pending" // "pending" / "done" / "error"
@action
fetchProjects() {
this.githubProjects = []
this.state = "pending"
fetchGithubProjectsSomehow().then(
// 内联创建的动作
action("fetchSuccess", projects => {
const filteredProjects = somePreprocessing(projects)
this.githubProjects = filteredProjects
this.state = "done"
}),
// 内联创建的动作
action("fetchError", error => {
this.state = "error"
})
)
}
}
- async / await写法
async / await其实就是promise的封装,已经被es支持,好处是可以使代码看起像是同步代码,易于理解。
需要注意的点是await必须写在async函数内部,不可单独存在。
class Store {
@observable githubProjects = []
@observable state = "pending" // "pending" / "done" / "error"
@action
async fetchProjects() {
this.githubProjects = []
this.state = "pending"
try {
const projects = await fetchGithubProjectsSomehow()
const filteredProjects = somePreprocessing(projects)
// await 之后,再次修改状态需要动作:
runInAction(() => {
this.state = "done"
this.githubProjects = filteredProjects
})
} catch (error) {
runInAction(() => {
this.state = "error"
})
}
}
}
- flows
此方案基本等同于async/await方法。其属于mobx内置概念,可取消这次异步,有兴趣的同学自行研究。 - 并发请求
方案1:使用promise.all()方法。
方案2:使用async/await,逐一执行异步 - 在异步执行完毕做一系列处理
方案1:定义好一个函数,作为回调传给action,异步完毕直接调用。
方案2:在action中返回一个promise
方案3:async/await 同步化
When,Reaction用法
两种方法均为state改变后的副作用
- when
when方法有两个参数。均为函数。方法会自动运行第一函数,知道第一个函数返回true,则运行第二个函数。所以第一个函数相当于一个检测函数,当state中的某些值变化为你需要的值时返回true,然后执行对应的响应函数。
经测试,第一个参数需要传递一个computed,来自动响应state变化。同时when需要写在constructor内,否则会被当成原生的when处理。
class MyResource {
constructor() {
when(
// 一旦...
() => !this.isVisible,
// ... 然后
() => this.dispose()
);
}
@computed get isVisible() {
// 标识此项是否可见
}
dispose() {
// 清理
}
}
- reaction
reaction接收接收两个参数,均为函数,第一个函数需要用到你要监测的state并且返回一个值,此返回值将作为第二个函数的参数,第二个函数接收两个参数,第一个为前一个函数的返回值,第二个参数为reaction,调用reaction.dispose方法可以清楚这个reaction
const todos = observable([
{
title: "Make coffee",
done: true,
},
{
title: "Find biscuit",
done: false
}
]);
// reaction 的错误用法: 对 length 的变化作出反应, 而不是 title 的变化!
const reaction1 = reaction(
() => todos.length,
length => console.log("reaction 1:", todos.map(todo => todo.title).join(", "))
);
// reaction 的正确用法: 对 length 和 title 的变化作出反应
const reaction2 = reaction(
() => todos.map(todo => todo.title),
titles => console.log("reaction 2:", titles.join(", "))
);
// autorun 对它函数中使用的任何东西作出反应
const autorun1 = autorun(
() => console.log("autorun 1:", todos.map(todo => todo.title).join(", "))
);
todos.push({ title: "explain reactions", done: false });
// 输出:
// reaction 1: Make coffee, find biscuit, explain reactions
// reaction 2: Make coffee, find biscuit, explain reactions
// autorun 1: Make coffee, find biscuit, explain reactions
todos[0].title = "Make tea"
// 输出:
// reaction 2: Make tea, find biscuit, explain reactions
// autorun 1: Make tea, find biscuit, explain reactions
const counter = observable({ count: 0 });
// 只调用一次并清理掉 reaction : 对 observable 值作出反应。
const reaction3 = reaction(
() => counter.count,
(count, reaction) => {
console.log("reaction 3: invoked. counter.count = " + count);
reaction.dispose();
}
);
counter.count = 1;
// 输出:
// reaction 3: invoked. counter.count = 1
counter.count = 2;
// 输出:
// (There are no logging, because of reaction disposed. But, counter continue reaction)
console.log(counter.count);
// 输出:
// 2
Mobx在项目中的应用方法
- 定义store
在src目录下的mudles中新建自己的文件夹,书写自己的store - 实例化store
在src/store目录下的index中导入自己的store文件,并new一个自己store的实例,new出来的实例的名字将作为往后在props中你自己store的对象key值。 - 将自己的store注入到自己的模块
import React, { Component } from "react";
import { observer,inject } from "mobx-react";
@inject('你自己store的名称,如果是多个store,用逗号隔开')
// 注意@observer后必须紧跟class,否则将不起效果
@observer
class Test extends React.Component {
render() {
return (
<div>
<childComponent store={this.props.yourStoreName}/>
</div>
);
}
}
export default Test;
import React, { Component } from "react";
import { observer } from "mobx-react";
@observer
class child extends React.Component {
render() {
return (
<div>
{this.props.store.arr}
</div>
);
}
}
export default child;
项目中只是用请参见命令行模块代码。注:命令行模块在render方法中先定义变量接收store中的值,再进行引用是为了方便代码阅读,更容易知道此组件引用了哪些store中的变量。
最后注意事项
Mobx中的数组,对象均不同于原生的数组和对象,均使用了proxy,但是并不影响使用,可以调任何原生的方法。如果需要转成的原生的,可以调用toJS方法,此方法接收一个proxy值,返回一个原生值。
后续mobx版本可能从5降为4(为了支持低版本浏览器),将为4之后mobx数组使用isArray将不能判断是否为数组,因此将mobx数组传入某些第三方插件时会出错,所以如果此值要传入第三方插件,希望大家都使用toJS包裹。
网友评论