跳到主要内容

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. 为什么有时候设置了新的数据, 使用数据的组件没有被刷新?

  1. 可能存在跨页面的情况, mobx 无法跨页面共享数据。 如需跨页面共享数据则在根页面进行注册, 例如在 App.tsx 中注册 StoreProvider。
  2. 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 的属性进行自定义,而不是在使用时进行处理,减少性能开销。

此篇维护者:黄振敏