背景
之前的两篇文章
简要说了下Mobx的原理和使用目的,这里说下在项目中Mobx使用的具体方式。
现有工程问题
1.逻辑和UI耦合
目前团队成员很多都是第一次写RN,之前也没有React相关开发经验,RN官网文档因为都是较简单的demo,所以会把业务逻辑直接写在UI组件里,导致简单的UI组件代码膨胀,难以维护和阅读。
2.避免网状通信和状态分层
随着一些业务的复杂化,有些业务会出现模块间的相互调用,这种网状调用,造成代码逻辑混乱,难以维护。 同时,我们希望数据、状态按照页面或者组件的生命周期维度进行划分,而不是使用类似Redux这种单一的全局状态进行管理,因为很多状态其实往往是一次性的,如果都使用统一的状态进行管理,会导致state的膨胀以及模板代码的增加。
3.全局数据处理困境
当父子组件需要共享一些状态时,这里有常见的两种方案
- 使props属性层层传递
使用props层层传递,大量中间组件需要帮忙处理传递这些无用属性,且如果需要传递参数或者增加 props ,都需要修改 A、B、中间组件 * n 个地方,增加代码维护难度。
- 使用context进行数据存储
context是React提供的数据存储方案,但是它有两个缺点,一是当数据过多时,需要使用多个provider为不同的context提供数据;二是当context的某个数据改变时,所有使用该context的组件都会重复刷新,无法进行细粒度的更新。
诉求
我们需要一种架构,它应该可以做到一下几点:
分离业务逻辑和UI处理。
解决网状调用,状态和数据可以按照组件生命周期组织管理。
提供一种细粒度的全局数据管理和传递方式。
实践
分离业务逻辑和UI处理
利用Mobx,创建业务store,store内部持有可观测状态(ui状态和逻辑状态),以及各种action,用来进行逻辑处理和状态更新。组件使用observer包装,监听Store中state的变化。
这里有一点需要注意,UIState的存在其实是展示元素对服务端下发元素的转换后在UI上的体现,它存在的意义就是防止接口在迭代过程中字段的变化导致UI改动范围的不可控,分离UI和服务端端字段,进行一层防腐。
export interface FirstPageUIState {
content: string;
showPanel:boolean;
}
export interface FirstPageLogicState {
isOpenExperiment: boolean;
}
/**
* page级别Store
*/
export class FirstPageStore {
uiState: FirstPageUIState;
logicState: FirstPageLogicState;
appStore: AppStore;
constructor(appStore: AppStore) {
this.appStore = appStore;
this.uiState = { content: '关闭实验',showPanel:false };
...
makeAutoObservable(this)
}
showPanel(){
this.uiState.showPanel = true;
}
get getBundleId(): string {
return this.appStore.appProps.bundleId;
}
updateExperiment() {
if (this.logicState.isOpenExperiment) {
this.uiState.content = '开启实验';
}
else {
this.uiState.content = '关闭实验';
}
this.logicState.isOpenExperiment = !this.logicState.isOpenExperiment;
}
}
store分层
按照生命周期为维度,设置不同类型的store,基本分为三种类型,全局、页面和复杂业务组件
- 全局store是整个RN项目生命周期级别的store,一般包括启动参数或者多个页面所需要的状态。
- 页面store则是每个RN页面生命周期级别的store,包括当前页面所需要的ui state和逻辑 state。
- 组件store则是每个page下对应的以模块的store,除非是较为复杂的组件,否则一般不需要。
通信规则
上层store可以依赖下层store,同层store间禁止通信,如果需要同层store进行通信,抽取对应的状态,下沉到下层的store。对于不是下层store所需的状态,可以通过在下层store编写listener,上层store注入listener实现通信。
import { makeAutoObservable } from 'mobx'
import { createContext } from 'react';
export interface AppProps {
liveId: string;
bundleId: string;
}
/**
* 全局状态Store
*/
export class AppStore {
appProps: AppProps;
listeners: Set<(() => void)>;
constructor(props) {
this.appProps = props;
this.listeners = new Set();
makeAutoObservable(this)
}
//注册回调
registerListener(listener: () => void) {
this.listeners.add(listener);
}
//移除回调
removeListener(listenre:()=>void){
this.listeners.delete(listenre);
}
//当调用该方法是,通知所有注入的回调,这里尤其适合处理一些弹窗。
notifyShowPanel(){
this.listeners.forEach(value=>{
value();
})
}
}
import { AppStore } from '@/mobxDemo/app/store/appStore';
import { makeAutoObservable } from 'mobx'
import { createContext } from 'react';
export interface FirstPageUIState {
content: string;
showPanel:boolean;
}
export interface FirstPageLogicState {
isOpenExperiment: boolean;
}
/**
* page级别Store
*/
export class FirstPageStore {
uiState: FirstPageUIState;
logicState: FirstPageLogicState;
appStore: AppStore;
constructor(appStore: AppStore) {
this.appStore = appStore;
this.uiState = { content: '关闭实验',showPanel:false };
this.logicState = { isOpenExperiment: false }
this.appStore.registerListener(this.showPanel);
makeAutoObservable(this)
}
unregisterListener(){
this.appStore.registerListener(this.showPanel);
}
showPanel(){
this.uiState.showPanel = true;
}
...
}
const firstPageContext = createContext<FirstPageStore>(null);
export { firstPageContext }
通过这种store的组织形式,避免网状调用,防止业务复杂后带来的代码劣化。
同时上面可以看到为每个store都创建了一个context,在对应store周期的组件内,创建对应的store,赋值给对应的context的provider,提供对应生命周期的store。
function FirstPage() {
//创建page页面的store
const firstPageStore = useLocalObservable(() => new FirstPageStore(appStore))
return (
<firstPageContext.Provider value={firstPageStore}>
<TouchableOpacity onPress={() => {
firstPageStore.updateExperiment();
}} >
<View>
<Text>{`${firstPageStore.uiState.content} bundleId is ${firstPageStore.getBundleId}`}</Text>
<TouchableOpacity onPress={() => {
firstPageStore.showPanel();
}}>
<Text>{firstPageStore.uiState.showPanel?'show panel':'not show'}</Text>
</TouchableOpacity>
</View>
</TouchableOpacity>
</firstPageContext.Provider>
)
}
细粒度的全局数据管理和传递方式
利用mobx的特性,可以解决context重新赋值而带来的非必要刷新。具体原理可以参考官方文档。 www.mobxjs.com/understandi…
关注我的公众号:’滑板上的老砒霜‘