一、为什么我们需要使用Mobx?
- Mobx 是 flux 实现的后起之秀. 以更简单的时候和更少的概念, 让 flux 使用起来变得更简单.
- 相比 Redux 有mutation, action, dispatch 等概念. Mobx则更加简洁, 更符合对Store 增删改查的操作概念.
- Mobx是学习成本更低,性能更好的状态解决方案。
- Mobx代码量少,与TS结合性更好,可以使用更少的代码实现更复杂的页面
- 使用好Mobx可以极大的降低页面的不必要的重绘次数
二、React Native工程集成Mobx以及TS环境的搭建
(1) 安装Mobx、Mobx-React
npm i mobx mobx-react
(2)安装TS
npm i typescript
(3)安装TS BaBle解析器
在package.json中输入如下依赖,并重新执行npm install
"devDependencies": {
"@babel/core": "^7.5.5",
"@babel/plugin-proposal-decorators": "^7.4.4",
"@babel/preset-typescript": "^7.3.3",
"@babel/runtime": "^7.5.5",
"@types/react-dom": "^16.8.5",
"@types/react": "^16.9.2",
"@types/react-native": "^0.60.7",
"babel-jest": "^24.9.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"jest": "^24.9.0",
"metro-react-native-babel-preset": "^0.56.0",
"react-test-renderer": "16.8.6"
},
(4)配置Bable插件
在.babelrc文件中应用以下插件脚本,如没有则在工程根目录新建
{
"presets": [
"@babel/preset-typescript",
[
"module:metro-react-native-babel-preset"
]
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
"transform-class-properties"
]
}
(5)配置TS校验规则
在tsconfig.json文件中应用以下校验配置,如没有则在工程根目录新建
{
"compilerOptions": {
"target": "es2017",
"module": "commonjs",
"jsx": "preserve",
"strict": true,
"noImplicitAny": false,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"esModuleInterop": true,
"removeComments": false,
"resolveJsonModule": true,
"isolatedModules": true,
"allowJs": true,
"checkJs": true
},
"exclude": [
"node_modules"
],
"types": [
"react",
"react-dom",
"react-native"
]
}
具体字段含义参照https://www.html.cn/doc/typescript/doc/handbook/tsconfig.json.html
三、Mobx中一些常用的概念
(1)observable和autorun
import { observable, autorun } from 'mobx';
const value = observable(0);
const number = observable(100);
autorun(() => {
console.log(value.get());
});
value.set(1);
value.set(2);
number.set(101);
可以看到,控制台中依次输出0,1,2。
observable可以用来观测一个数据,这个数据可以数字、字符串、数组、对象等类型(相关知识点具体会在后文中详述),而当观测到的数据发生变化的时候,如果变化的值处在autorun中,那么autorun就会自动执行。
上例中的autorun函数中,只对value值进行了操作,而并没有number值的什么事儿,所以number.set(101)这步并不会触发autorun,只有value的变化才触发了autorun。
(2)计算属性——computed
假如现在我们一个数字,但我们对它的值不感兴趣,而只关心这个数组是否为正数。这个时候我们就可以用到computed这个属性了。
const number = observable(10);
const plus = computed(() => number.get() > 0);
autorun(() => {
console.log(plus.get());
});
number.set(-19);
number.set(-1);
number.set(1);
依次输出了true,false,true。
第一个true是number初始化值的时候,10>0为true没有问题。
第二个false将number改变为-19,输出false,也没有问题。
但是当-19改变为-1的时候,虽然number变了,但是number的改变实际上并没有改变plus的值,所以没有其它地方收到通知,因此也就并没有输出任何值。
直到number重新变为1时才输出true。
实际项目中,computed会被广泛使用到。
(3)action,runInAction
mobx推荐将修改被观测变量的行为放在action中。
来看看以下例子:
import {observable, action} from 'mobx';
class Store {
@observable number = 0;
@action add = () => {
this.number++;
}
}
const newStore = new Store();
newStore.add();
以上例子使用了ES7的decorator,在实际开发中非常建议用上它,它可以给你带来更多的便捷
接下来说一个重点action只能影响正在运行的函数,而无法影响当前函数调用的异步操作
比如官网中给了如下例子
@action createRandomContact() {
this.pendingRequestCount++;
superagent
.get('https://randomuser.me/api/')
.set('Accept', 'application/json')
.end(action("createRandomContact-callback", (error, results) => {
if (error)
console.error(error);
else {
const data = JSON.parse(results.text).results[0];
const contact = new Contact(this, data.dob, data.name, data.login.username, data.picture);
contact.addTag('random-user');
this.contacts.push(contact);
this.pendingRequestCount--;
}
}));
}
重点关注程序的第六行。在end中触发的回调函数,被action给包裹了,这就很好验证了上面加粗的那句话,action无法影响当前函数调用的异步操作,而这个回调毫无疑问是一个异步操作,所以必须再用一个action来包裹住它,这样程序才不会报错。
如果你使用async function来处理业务,那么我们可以使用runInAction这个API来解决之前的问题。
import {observable, action, useStrict, runInAction} from 'mobx';
useStrict(true);
class Store {
@observable name = '';
@action load = async () => {
const data = await getData();
runInAction(() => {
this.name = data.name;
});
}
}
四、结合React Native 使用
在React中,我们一般会把和页面相关的数据放到state中,在需要改变这些数据的时候,我们会去用setState这个方法来进行改变。
先设想一个最简单的场景,页面上有个数字0和一个按钮。点击按钮我要让这个数字增加1,就让我们要用Mobx来处理这个试试。
import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'
import React, {PureComponent} from 'react'
import {observer} from 'mobx-react'
import {action, observable} from 'mobx'
class MyState {
@observable num = 1
@action
public addNum() {
this.num++
}
}
const state = new MyState()
interface Props {
}
interface State {
}
/**
* 注释:
* 时间: 2019/8/21 0021 11:52
* @author 郭翰林
*/
@observer
export default class App extends PureComponent {
static propTypes = {}
constructor(props) {
super(props)
}
render() {
return (
{state.num}
{
state.addNum()
}}>
增加
)
}
}
const styles = StyleSheet.create({
buttonStyle: {
justifyContent: "center",
alignItems: "center",
width: 250,
borderRadius: 8,
height: 50,
marginBottom: 25,
backgroundColor: '#fc704e'
}
})
上例中我们使用了一个MyState类,在这个类中定义了一个被观测的num变量和一个action函数addNum来改变这个num值。
之后我们实例化一个对象,叫做newState,之后在我的React组件中,我只需要用@observer修饰一下组件类,便可以愉悦地使用这个newState对象中的值和函数了。
跨组件交互
在不使用其它框架、类库的情况下,React要实现跨组件交互这一功能相对有些繁琐。通常我们需要在父组件上定义一个state和一个修改该state的函数。然后把state和这个函数分别传到两个子组件里,在逻辑简单,且子组件很少的时候可能还好,但当业务复杂起来后,这么写就非常繁琐,且难以维护。而用Mobx就可以很好地解决这个问题。来看看以下的例子:
class MyState {
@observable num1 = 0;
@observable num2 = 100;
@action addNum1 = () => {
this.num1 ++;
};
@action addNum2 = () => {
this.num2 ++;
};
@computed get total() {
return this.num1 + this.num2;
}
}
const newState = new MyState();
const AllNum = observer((props) => num1 + num2 = {props.store.total});
const Main = observer((props) => (
num1 = {props.store.num1}
num2 = {props.store.num2}
));
@observer
export default class App extends React.Component {
render() {
return (
);
}
}
有两个子组件,Main和AllNum (均采用无状态函数的方式声明的组件)
在MyState中存放了这些组件要用到的所有状态和函数。
之后只要在父组件需要的地方实例化一个MyState对象,需要用到数据的子组件,只需要将这个实例化的对象通过props传下去就好了。
那如果组件树比较深怎么办呢?
查看最新Mobx5的@inject属性
关于@observer的一些说明
通常,在和Mobx数据有关联的时候,你需要给你的React组件加上@observer,你不必太担心性能上的问题,加上这个@observer不会对性能产生太大的影响,而且@observer还有一个类似于pure render的功能,甚至能起到性能上的一些优化。
所谓pure render见下例:
@observer
export default class App extends React.Component {
state = {
a: 0,
};
add = () => {
this.setState({
a: this.state.a + 1
});
};
render() {
return (
{this.state.a}
);
}
}
@observer
class PureItem extends React.Component {
render() {
console.log('PureItem的render触发了');
return (
你们的事情跟我没关系
);
}
}
如果去掉子组件的@observer,按钮每次点击,控制台都会输出 PureItem的render触发了 这句话。
React组件中可以直接添加@observable修饰的变量
@observer
class MyComponent extends React.Component {
state = { a: 0 };
@observable b = 1;
render() {
return(
{this.state.a}
{this.b}
)
}
}
在添加@observer后,你的组件会多一个生命周期componentWillReact。当组件内被observable观测的数据改变后,就会触发这个生命周期。
注意setState并不会触发这个生命周期!state中的数据和observable数据并不算是一类。
另外被observable观测数据的修改是同步的,不像setState那样是异步,这点给我们带了很大便利。
五、总结
Mobx想要入门上手可以说非常简单,只需要记住少量概念并可以完成许多基础业务了。但深入学习下去,也还是要接触许多概念的。例如Modifier、Transation等等。
最后与Redux做一个简单的对比
1、Mobx写法上更偏向于OOP
2、对一份数据直接进行修改操作,不需要始终返回一个新的数据
3、对typescript的支持更好一些
4、相关的中间件很少,逻辑层业务整合是一个问题