当一个男人决定使用Mobx(三)

6,239 阅读4分钟

背景

之前的两篇文章

男人决定使用Mobx(一)

男人决定使用Mobx(二)

简要说了下Mobx的原理和使用目的,这里说下在项目中Mobx使用的具体方式。

现有工程问题

1.逻辑和UI耦合

目前团队成员很多都是第一次写RN,之前也没有React相关开发经验,RN官网文档因为都是较简单的demo,所以会把业务逻辑直接写在UI组件里,导致简单的UI组件代码膨胀,难以维护和阅读。

2.避免网状通信和状态分层

随着一些业务的复杂化,有些业务会出现模块间的相互调用,这种网状调用,造成代码逻辑混乱,难以维护。 同时,我们希望数据、状态按照页面或者组件的生命周期维度进行划分,而不是使用类似Redux这种单一的全局状态进行管理,因为很多状态其实往往是一次性的,如果都使用统一的状态进行管理,会导致state的膨胀以及模板代码的增加。

3.全局数据处理困境

当父子组件需要共享一些状态时,这里有常见的两种方案

  1. 使props属性层层传递

使用props层层传递,大量中间组件需要帮忙处理传递这些无用属性,且如果需要传递参数或者增加 props ,都需要修改 A、B、中间组件 * n 个地方,增加代码维护难度。

  1. 使用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…

关注我的公众号:’滑板上的老砒霜‘