给 RN 添加 mobx
背景
项目组中 RN 之前使用的状态管理是小伙伴自行封装 redux, redux-saga 的一个组件库,然而由于文档不全,使用起来不方便等原因,经过讨论,决定替换成 mobx。主要参考官方文档: https://mobx.js.org/react-integration.html#react-integration。本文记录我将 mobx 加入 RN 的过程以及使用 mobx 的一些注意事项(实际上与普通的 React 项目差不多)
过程
在 src 目录下新增一个 stores 文件夹用于存放 mobx 相关的代码
编写 observable state,这里我写了一个 Counter.ts 并且新建 stores.ts 管理所有的状态:
// Counter.ts import { makeAutoObservable } from 'mobx'; class Counter { count = 0; constructor() { makeAutoObservable(this); } // 注意要使用箭头函数,否则需要用 action.bound 来绑定 this,否则指向 React 组件 increase = () => { this.count += 1; }; decrease = () => { this.count -= 1; }; } export default Counter; // stores.ts import Counter from './Counter'; export default { counter: new Counter(), };根据 mobx 文档,组件要使用外部状态有三种写法:
- 通过 props 将 状态传递到组件
- 直接通过 import 进来
- 通过 React context 引入到组件
为了方便使用,考虑 2,3 中写法,文档中推荐使用 context,那么就这里尝试使用 context。
编写 withStores.ts 函数,这是一个高阶组件,主要的作用是给根元素用 Provider 包裹起来,并且根元素通常也是需要 observe 的,因此这里也顺便将其转成可观测的组件:
// withStores.ts import React, { createContext } from 'react'; import { observer } from 'mobx-react-lite'; import stores from './stores'; // 初始化 Context export const StoresContext = createContext<typeof stores>(stores); const withStores = <P extends object>( WrappedComponent: React.FunctionComponent<P>, ) => (props: P) => { // 转成可观测的组件 const ObserverWrappedComponent = observer(WrappedComponent); return ( <StoresContext.Provider value={stores}> <ObserverWrappedComponent {...props} /> </StoresContext.Provider> ); }; export default withStores;接下来的使用就简单了:
import React, { useEffect, useContext } from 'react'; import { TouchableOpacity, View, Text } from 'react-native'; import { withStores, StoresContext } from '@/stores'; import Child from './Child'; const MobxDemo = () => { const { counter } = useContext(StoresContext); return ( <View> <Text>count: {counter.count}</Text> <TouchableOpacity onPress={counter.increase}> <Text>increase: +</Text> </TouchableOpacity> <TouchableOpacity onPress={counter.decrease}> <Text>decrease: -</Text> </TouchableOpacity> <Child /> </View> ); }; export default withStores(MobxDemo); // Child.tsx import React, { useContext } from 'react'; import { observer } from 'mobx-react-lite'; import { Text } from 'react-native'; import { StoresContext } from '@/stores'; export const Child = () => { const { counter } = useContext(StoresContext); return <Text>child: {counter.count}</Text>; }; export default observer(Child);