对全局通用数据的一次思考
目前所开发的中后台项目的技术栈都是react + dva
。而随着时间的推移,中后台项目积累了越来越多的通用数据... 这些通用数据通常是由不同的接口来获取的。由于接口数量很多,会引起项目管理上的混乱,也会造成其他心智负担。
# dva数据管理所存在的问题
具体体现可以看下图:
# dva store 中定义接口获取数据
通过代码体现,可以看到服务端每增加一个接口,在store
中都需要定义接口触发行为,然后还要定义如何存储接口返回数据... 这种方式在大量接口场景下,是非常不明智的。
interface GetAllModelType {
namespace: 'getAll';
state: GetAllModelState;
effects: {
fetchAllProduct: Effect;
fetchAllProductGroup: Effect;
fetchAllFactory: Effect;
fetchAllExpress: Effect;
fetchAllOperators: Effect;
fetchNewAllOperators: Effect;
fetchAllMerchants: Effect;
fetchAllProductCategory: Effect;
fetchAllMateriel: Effect;
fetchAllAuthRole: Effect;
fetchAllFactoryConfigs: Effect;
fetchAllAutoDispatchSpu: Effect;
fetchAllExpressCompany: Effect;
... 省略了很多
};
reducers: {
saveNewAllOperators: SaveFuncion<{ allNewOperator: SelectOptionData[]; qpmOperator: [] }>;
saveAllMerchants: SaveFuncion;
saveAllProductCategory: SaveFuncion;
saveAllMateriel: SaveFuncion;
saveAllFactoryConfigs: SaveFuncion;
saveAutoDispatchSpu: SaveFuncion;
saveAllExpressCompany: SaveFuncion;
saveAllSortCenter: SaveFuncion<(SelectOptionData & { createdAt: number; updatedAt: string })[]>;
saveAllSpu: SaveFuncion;
saveAllProductUnit: SaveFuncion<
(SelectOptionData & { createdAt: string; updatedAt: string })[]
>;
saveAllOrganizer: SaveFuncion;
saveSupplier: SaveFuncion;
... 省略了很多
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# hooks中获取store的数据
在hooks
中,去拿store
中存储的数据,每一个变量都需要分别做两件事情。触发store
中定义的接口调用行为以及获取store
中存储的数据。这种触发store
中定义的接口行为,其实存在大量的重复代码... 是可以进行优化的
import { useOnceGetAll } from '@/hooks/getAll';
import { useSelector } from 'umi';
useSelectSource.symbolKey = Symbol('ProduceEditRecord');
export function useSelectSource() {
const allSpu = useSelector((state: ConnectState) => state.getAll.allProductGroup);
const allOperator = useSelector((state: ConnectState) => state.getAll.allOperator);
const allFactory = useSelector((state: ConnectState) => state.getAll.allFactory);
useOnceGetAll((dispatch) => {
if (!allSpu.length) {
dispatch({
type: 'getAll/fetchAllProductGroup',
});
}
if (!allOperator.length) {
dispatch({
type: 'getAll/fetchAllOperators',
});
}
if (!allFactory.length) {
dispatch({
type: 'getAll/fetchAllFactory',
});
}
}, useSelectSource.symbolKey);
return {
allSpu,
allOperator,
allFactory,
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# page中消费数据
在pages
中消费hooks
数据还是挺简洁的,基本一行语句即可完成操作
const { allOperator, allSpu, allFactory } = useSelectSource();
通过上述的了解,我们是把dva
中的数据分为管理、获取、消费三个阶段。在这三个阶段中,其中管理和获取是存在优化空间的,管理这个阶段是在管理接口行为和数据存储行为。其中数据存储行为由于之前针对每一个接口都定义了reducer
逻辑,当接口较少的时候还是很简洁明了的。然而,接口多的时候,就太多冗余逻辑了,真的没啥必要。
获取store
中数据的这个阶段,也是存在太多冗余代码,更好的方式是定义好获取主键。在每次需要获取的数据的时候,只需要传入主键即可,这样就会大大提高了使用效率。
# 新的方式如下
在新的提案下,看似我们只新增了一个文件,用于管理接口调用行为。由于这个文件的新增,可以减少store
中的代码量,以及hooks
中的代码量。
# store中定义接口行为与数据存储行为
代码上的形式可以这样体现,在store
中只定义了一次接口调用行为,一次数据存储行为。
interface GetAllModel {
namespace: 'getAll';
state: Model.GetAll;
effects: { fetch: Effect; };
reducers: { save: Model.GetAllReducer<Record<keyof Model.GetAll, any>> };
}
2
3
4
5
6
# hooks中使用方式的变化
可以看到,由于store
中只有一个触发接口的调用行为,那么在hooks
中想要触发接口的调用行为,则只需要传入对应的接口名称即可。而对于众多的接口名称该如何更友好的记住它们呢?这里就提现了TypeScript
的强大。
在类型系统中定义方法名称
在使用useOnceGetAll
方法时提供具体的方法名称推导。这样既可以方便开发,又可以降低出错概率。具体定义方式如下
type FetchNames = (
| 'Meta'
| 'Storages'
| 'Express'
| 'Spu'
| 'StoragesV2'
| 'ExpressCompany'
| 'Role'
| 'StockArea'
| 'StockLocation'
| 'ExpressBranch'
| 'SortSpu'
| 'QualityProjects'
| 'Factorys'
| 'Suppliers'
)[]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
hooks
中的使用如下
export function useSelectSource() {
const { allSpu, creators, factorys } = useSelector((state: Model.State) => state.getAll);
useOnceGetAll(
['Meta', 'Spu', 'QualityProjects', 'Factorys', 'Suppliers'],
useSelectSource.symbol,
);
return {
allSpu,
creators,
factorys,
suppliers,
problems: qualitys.map((item) => ({ name: `${item.name}`, id: `${item.id}` })),
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ModelUtil中的处理方式
在新增的文件modelUtil
中,其中最主要的方法有两个。其一,并发请求目标接口。其二,将获取的目标接口数据返回到外部。请看看以下的代码定义。
class ModelUtil {
public async fetchAll(names: Model.FetchNames) {
return Promise.all(names)
}
public async getAll(names: Model.FetchNames) {
const [, result] = await to(this.fetchAll(names));
if (!result || !result.length) return {};
const modelState: Partial<Model.GetAll> = result.reduce(() =>, {});
}
// ... 定义相关接口请求行为
}
2
3
4
5
6
7
8
9
10
11
由于在pages
中消费hooks
中的数据,本身就很精简。所以,没有针对pages
的逻辑改动。
# 总结
- 该方案可以精简
store
中定义接口调用行为,又可以精简store
中存储数据的行为 - 由于
store
中的接口调用行为只有一个,所以极大减少了hooks
中代码逻辑... 非常nice - 唯一问题是,每一个接口还是要去定义对应的调用行为... 这是无法避免的事情... 除非服务端只通过一个接口来返回通用数据
在文章中出现的useOnceGetAll
方法将会由另一篇文章来介绍。其实看名字也能猜个大概,就是如何保证该数据在多个地方使用,而只需要调用一次。