7. 状态管理
项目上统一使用 Redux
和 Mobx V4
作为状态管理库。除非跨页面情况下,正常情况下使用 Mobx
作为状态管理。
7.1. 可观察组件
必须对于使用到 mobx 数据
的函数组件,通过 mobx-react-lite
,提供的 observer
方法包裹,让函数组件变成可观察组件。
import React , { useMeme } from 'react';
import { observer } from "mobx-react-lite";
import { DataSet, Table } from "choerodon-ui/pro";
interface Props {
message: string;
}
const App: React.FC<Props> = ({ message }) => {
const userDs = useMeme(()=> new DataSet({
//......
}));
return (
<Table
key="basic"
rowNumber={({ text }) => `#${text}`}
dataSet={userDs}
columns={columns}
/>
)};
export default observer(App);
如果可观察组件需要使用 forwardRef
向上传递 ref
,则可以使用 mobx-react
提供的 useObserver
方法包装函数组件。
import React, { forwardRef, useImperativeHandle } from 'react';
import { useLocalStore, useObserver } from 'mobx-react';
import {Button} from 'antd';
interface Props {
defaultCount: number;
}
interface ResultRefType {
getCount: number, // 向上返回的 ref 类型
}
const ForwardRef = forwardRef<ResultRefType, Props>(({defaultCount}, ref) => {
const store = useLocalStore(() => ({
count: defaultCount,
}));
useImperativeHandle(ref, () => {
return ({
getCount: () => {
return count;
},
});
});
return (useObserver(() => <div>
{store.count}
<Button onClick={()=>{store.count++;}}>change ref count</Button>
</div>)
);
});
export default ForwardRef;
7.2. Mobx Store
定义
必须 (此规则针对 aPaaS 项目)mobx
的可观察状态无法跨页面进行共享,因此按照一个页面一个共享 store
的方式来定义。对于复杂组件
允许定义其局部 store
。store
创建模板如下:
stores/index.tsx
import React, { createContext, useContext } from "react";
import { useLocalStore } from "mobx-react-lite";
import { toJS } from "mobx";
import { set, forOwn, isObject } from "lodash";
interface LocalStoreState {
name: string;
}
export interface ScriptEventStore {
state: LocalStoreState;
setState: (<T extends keyof LocalStoreState>(key: T, value: LocalStoreState[T]) => void)
& (<T extends keyof LocalStoreState>(key: {[k in T]: LocalStoreState[T]}) => void);
getState: <T extends keyof LocalStoreState>(key: T, isToJs?: boolean) => LocalStoreState[T];
}
const Store = createContext<ScriptEventStore>({} as ScriptEventStore);
function StoreProvider(props) {
const { children } = props;
const store = useLocalStore<ScriptEventStore>(() => ({
state: {
name: "aPaaS"
},
setState<T extends keyof LocalStoreState>(keys: T | { [k in T]: LocalStoreState[T] }, value?: LocalStoreState[T]) {
if (isObject(keys)) {
forOwn(keys, (v, k) => {
set(store.state, k, v);
});
} else {
set(store.state, keys, value);
}
},
getState(key, isToJs?) {
return isToJs ? toJS(store.state[key]) : store.state[key];
},
}));
return <Store.Provider value={store}>{children}</Store.Provider>;
}
const useStore = () => {
const store = useContext(Store);
if (!store) {
throw new Error("You have forgot to use StoreProvider.");
}
return store;
};
export { StoreProvider, useStore };
对于共享数据状态的定义,应当要求严格的数据类型,尽可能减少宽泛的数据类型,尤其是 any
类型。例如,例子中 setState
的定义,就严格限制了 key
和 value
的类型映射。
禁止直接修改可观察数据,必须通过定义的 setState
方法来修改数据,以便追踪数据修改者。
在该路由页面顶层组件,注册 StoreProvider
,同时顶层组件不应该包含任何业务逻辑代码,以免影响 Store
的状态。
import React from 'react';
import { StoreProvider } from './store';
import EditPage from './EditPage'; // 真正的页面
export default function () {
return (
<StoreProvider>
<EditPage />
</StoreProvider>
);
}
在 EditPage
中,通过我们定义的 store
暴露出来的 useStore
方法获取共享的可观察数据。
import React from 'react';
import { observer } from "mobx-react-lite";
import { useStore } from '../store';
interface Props {
}
const App: React.FC<Props> = () => {
const store = useStore();
return <div>{store.getState("name")}</div>
};
export default observer(App);
在 C7N
的 Modal
的 children
组件中使用 useStore
时需要注意, 在使用 Modal 的组件的地方,最外层需要包裹一下 ModalProvider
,才可以拿到 store 中定义的共享数据。
如果一个业务功能文件下包含多个页面,则 stores 文件夹
下创建不同页面的 store 文件,例:
stores/A.tsx
stores/B.tsx
FAQ
1. 为什么有时候设置了新的数据, 使用数据的组件没有被刷新?
- 可能存在跨页面的情况, mobx 无法跨页面共享数据。 如需跨页面共享数据则在根页面进行注册, 例如在
App.tsx
中注册StoreProvider
。 store
未初始化该数据。如果数据不存在初始值, 也必须在store
中定义该数据的空值
, 否则无法被observer
首次变化时监听到。
7.3. dataSet
文件管理
提案建议以一个功能页面为单位
,每个功能页面下创建一个 datasets
文件,该文件用于管理该功能页面的所有 dataSet
数据,文件的命名形式以 XXXDS.ts
, 例:
datasets/aaaDS.ts
datasets/bbbDS.ts
如果某个组件设计比较复杂,需要使用多个 dataSet
数据,则可以在该组件下创建 datasets
文件夹进行管理。
使用 datasets
作为文件名,是为了和 store
文件做区分。
stores
文件夹用来定义 mobx 共享状态数据。datasets
文件夹用来定义 C7N 的 dataSet 数据。
在 XXXDS.ts
,文件中不要直接 new
一个 DataSet 实例
,而是仅返回 DataSetProps
数据格式的对象,由使用者去创建 DataSet 实例
。
这样处理使得使用者还可以在创建时对 DataSet
的属性进行自定义,而不是在使用时进行处理,减少性能开销。