跳到主要内容

7. 状态管理

项目上统一使用 ReduxMobx 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 的方式来定义。对于复杂组件允许定义其局部 storestore 创建模板如下:

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 的定义,就严格限制了 keyvalue 的类型映射。

禁止

禁止直接修改可观察数据,必须通过定义的 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);
提示

C7NModalchildren 组件中使用 useStore 时需要注意, 在使用 Modal 的组件的地方,最外层需要包裹一下 ModalProvider ,才可以拿到 store 中定义的共享数据。

必须

如果一个业务功能文件下包含多个页面,则 stores 文件夹下创建不同页面的 store 文件,例:

    stores/A.tsx
stores/B.tsx

FAQ

1. 为什么有时候设置了新的数据, 使用数据的组件没有被刷新?

  1. 可能存在跨页面的情况, mobx 无法跨页面共享数据。 如需跨页面共享数据则在根页面进行注册, 例如在 App.tsx 中注册 StoreProvider
  2. store 未初始化该数据。如果数据不存在初始值, 也必须在 store 中定义该数据的空值, 否则无法被 observer 首次变化时监听到。

7.3. dataSet 文件管理

提案

建议以一个功能页面为单位,每个功能页面下创建一个 datasets 文件,该文件用于管理该功能页面的 dataSet 数据,文件的命名形式以 XXXDS.ts, 例:

    dataSet/aaaDS.ts
dataSet/bbbDS.ts
备注

使用 datasets 作为文件名,是为了和 store 文件做区分。

  • store 文件夹用来定义 mobx 共享状态数据。
  • datasets 文件夹用来定义 C7N 的 dataSet 数据。

XXXDS.ts,文件中不要直接 new 一个 DataSet 实例,而是仅返回 DataSetProps 数据格式的对象,由使用者去创建 DataSet 实例

这样处理使得使用者还可以在创建时对 DataSet 的属性进行自定义,而不是在使用时进行处理,减少性能开销。

此篇维护者:黄振敏