RN引入mobx状态管理库,本文根据自己在项目中真实使用场景讲述如何快速接入mobx
Mobx和Redux都是应用于React工程优秀的状态管理库,可以解决我们跨页面跨组件间通信问题。不过理念有所不同,Redux推崇状态树的概念,认为一个应用中只应该存在一个状态库,通过Provider
将store
挂载到视图顶层,使用的组件通过connect
方式用props
来访问需要的状态属性。而Mobx则并不强调状态树的概念,而是可以各自模块使用各自的状态库,不过也同样可以通过Provider
将状态挂载到顶层视图,在消费组件中通过inject
获取到props
状态。 具体差异可以到官网上了解
我们引入两个库mobx
和react-mobx-lite
(不使用react-mobx
,因为mobx
+mobx-react-lite
bundle体积增加67K,而mobx
+mobx-react
bundle体积增加80K,当然,redux
+react-redux
的bundle体积增加27K)
yarn mobx react-mobx-lite
依赖添加后,package.json中的dependencies
如下
"dependencies": {
"mobx": "^6.3.8",
"mobx-react-lite": "^3.2.2",
"react": "17.0.2",
"react-native": "0.66.3"
},
在定义store
的方式上基本都是一致的,不同的是使用方式,可以按使用store
是否共享、使用store
是Component
组件还是函数
组件、store
是否在顶层组件树上挂载来进行区分.
当然,不论项目中如何使用,定义store
的方式都是一样的
mobx
库常用的observable
、action
、computed
定义观察属性和修改属性的方法,在 6.x
版本之后,需要通过makeAutoObservable
或makeObservable
声明观察对象,可以参考一篇博文:mobx升级到6后组件不刷新import {action, makeObservable, observable, computed} from 'mobx';
export class CountStore {
count = 0; // 数量
price = 10; // 单价
constructor() {
// makeAutoObservable(this);
makeObservable(this, {
count: observable,
setCount: action,
totalPrice: computed,
});
}
get totalPrice() {
//总价,为了模拟因为count变化导致总价变化的情况,使用computed,有点类似vue中的computed计算属性
return this.count * price;
}
setCount(count) {
if (count >= 0) {
this.count = count;
}
}
}
CountComponent
组件是一个公共的组件,为了更快捷地示范以下store
应用场景而封装的store
observable
+action
+observer
store
实例:通过new Store()
创建出一个store
的实例mobx-react-lite
库的observer
定义组件store消费代码:
import React from 'react';
import {observer} from 'mobx-react';
import {CountStore} from './store/countStore';
import {CountComponent} from './common/components/CountComponent';
const countModel = new CountStore();
// 函数组件方式
export const CountMobxPage = observer(props => {
return (
<CountComponent
title={'CountMobxPage'}
count={countModel.count}
price={countModel.price}
totalPrice={countModel.totalPrice}
onPressDescrease={() => countModel.setCount(countModel.count - 1)}
onPressInscrease={() => countModel.setCount(countModel.count + 1)}
/>
);
});
// class组件方式
export const CountMobxClassPage = observer(
class extends React.Component {
render() {
return (
<CountComponent
title={'CountMobxClassPage'}
count={countModel.count}
price={countModel.price}
totalPrice={countModel.totalPrice}
onPressDescrease={() => countModel.setCount(countModel.count - 1)}
onPressInscrease={() => countModel.setCount(countModel.count + 1)}
/>
);
}
},
);
// 使用代码
/** 其他代码...**/
render(){
return(
<>
<CountMobxPage/>
<CountMobxClassPage/>
</>
)
}
store
(类似state的效果)使用独立store
时,store
的生命周期和组件声明周期一致,在组件销毁时,store
也会被系统回收销毁,不会造成内存污染
在使用独立store
时,函数
组件和class
组件的方式差异较大。
store
需要结合React.useRef
使用,这样才能进行实例隔离,同时需要注意的是,不能直接使用useRef(new Store())
,这样会造成不断创建一个新的store
实例,虽然这不会替换掉ref
的current
,但是这是无意义的消耗
export const CountMobxSelfPage = observer(props => {
const mobxRef = React.useRef(null);
if (!mobxRef.current) {
mobxRef.current = new CountStore();
}
return (
mobxRef.current.setCount(mobxRef.current.count - 1)
}
onPressInscrease={() =>
mobxRef.current.setCount(mobxRef.current.count + 1)
}
/>
);
});
store
在constructor
实例化class
组件时,将store
实例化
export const CountMobxSelfClassPage = observer(
class extends React.Component {
constructor(props) {
super(props);
this.store = new CountStore();
}
render() {
return (
this.store.setCount(this.store.count - 1)}
onPressInscrease={() => this.store.setCount(this.store.count + 1)}
/>
);
}
},
);
Provider
和inject
共享store
这种方式和redux
的状态树理念有些类似,都是通过Provider
来共享状态库,不过mobx
可以有多个store
,并且inject
消费的类如果要修改store
的属性,应该将store
作为props
传递给要使用组件,将action
的方法通过props
传递给子组件后调用并不能影响视图的更新。
const providerStore = new CountStore();
export const MobxPropsPage = props => {
return (
MobxPropsPage-使用Provider和inject方式使用store库
);
};
const MobxPropsUsePage = inject('store')(
observer(props => {
return (
props.store.setCount(props.store.count - 1)}
onPressInscrease={() => props.store.setCount(props.store.count + 1)}
/>
);
}),
);
const MobxPropsUseClassPage = inject('store')(
observer(
class extends React.Component {
render() {
return (
this.props.store.setCount(this.props.store.count - 1)
}
onPressInscrease={() =>
this.props.store.setCount(this.props.store.count + 1)
}
/>
);
}
},
),
);
import {View, Text, TouchableOpacity} from 'react-native';
import {commonStyles} from '../styles/commonStyles';
import {PropTypes} from 'prop-types';
export function CountComponent(props) {
const {title, count, price, totalPrice, onPressDescrease, onPressInscrease} =
props;
return (
{title}
数量:
onPressDescrease()}>
-
{count}
onPressInscrease()}>
+
{`单价:${price}`}
{`总价:${totalPrice}`}
);
}
CountComponent.propTypes = {
title: PropTypes.string,
count: PropTypes.number,
price: PropTypes.number,
totalPrice: PropTypes.number,
onPressDescrease: PropTypes.func,
onPressInscrease: PropTypes.func,
};
import {StyleSheet} from 'react-native';
export const commonStyles = StyleSheet.create({
contain: {
borderBottomColor: '#CCC',
borderBottomWidth: 1,
paddingHorizontal: 15,
paddingVertical: 5,
},
viewContain: {flexDirection: 'row', alignItems: 'center'},
titleText: {fontSize: 17, fontWeight: 'bold', color: '#333'},
text: {
fontSize: 16,
backgroundColor: '#FFF',
lineHeight: 22,
paddingHorizontal: 40,
},
countText: {
fontSize: 16,
color: '#981122',
paddingHorizontal: 20,
minWidth: 40,
},
textContain: {padding: 5, alignItems: 'center', flexDirection: 'row'},
priceText: {fontSize: 15},
totalText: {marginLeft: 20, fontSize: 15, color: '#222', fontWeight: 'bold'},
priceContain: {margin: 10, flexDirection: 'row', alignItems: 'center'},
});