十号

vuePress-theme-reco 十号    2022
十号

Choose mode

  • dark
  • auto
  • light
主页
分类
  • 《小狗钱钱》
  • 浏览器
  • Docker
  • note
  • 微前端
  • javascript
  • React
  • 工具
  • 工具函数
  • vue
TimeLine
简介

十号

20

Article

19

Tag

主页
分类
  • 《小狗钱钱》
  • 浏览器
  • Docker
  • note
  • 微前端
  • javascript
  • React
  • 工具
  • 工具函数
  • vue
TimeLine
简介

对全局通用数据的一次思考

vuePress-theme-reco 十号    2022

对全局通用数据的一次思考

十号 2021-09-18 工具函数web

目前所开发的中后台项目的技术栈都是react + dva。而随着时间的推移,中后台项目积累了越来越多的通用数据... 这些通用数据通常是由不同的接口来获取的。由于接口数量很多,会引起项目管理上的混乱,也会造成其他心智负担。

# dva数据管理所存在的问题

具体体现可以看下图:

浏览器DNS查询流程

# 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;
    ... 省略了很多
  };
}
1
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,
  };
}
1
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();
1

通过上述的了解,我们是把dva中的数据分为管理、获取、消费三个阶段。在这三个阶段中,其中管理和获取是存在优化空间的,管理这个阶段是在管理接口行为和数据存储行为。其中数据存储行为由于之前针对每一个接口都定义了reducer逻辑,当接口较少的时候还是很简洁明了的。然而,接口多的时候,就太多冗余逻辑了,真的没啥必要。

获取store中数据的这个阶段,也是存在太多冗余代码,更好的方式是定义好获取主键。在每次需要获取的数据的时候,只需要传入主键即可,这样就会大大提高了使用效率。

# 新的方式如下

在新的提案下,看似我们只新增了一个文件,用于管理接口调用行为。由于这个文件的新增,可以减少store中的代码量,以及hooks中的代码量。 浏览器DNS查询流程

# store中定义接口行为与数据存储行为

代码上的形式可以这样体现,在store中只定义了一次接口调用行为,一次数据存储行为。

interface GetAllModel {
  namespace: 'getAll';
  state: Model.GetAll;
  effects: { fetch: Effect; };
  reducers: { save: Model.GetAllReducer<Record<keyof Model.GetAll, any>> };
}
1
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'
)[]
1
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}` })),
  };
}
1
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(() =>, {});
  }
  // ... 定义相关接口请求行为
}
1
2
3
4
5
6
7
8
9
10
11

由于在pages中消费hooks中的数据,本身就很精简。所以,没有针对pages的逻辑改动。

# 总结

  • 该方案可以精简store中定义接口调用行为,又可以精简store中存储数据的行为
  • 由于store中的接口调用行为只有一个,所以极大减少了hooks中代码逻辑... 非常nice
  • 唯一问题是,每一个接口还是要去定义对应的调用行为... 这是无法避免的事情... 除非服务端只通过一个接口来返回通用数据

在文章中出现的useOnceGetAll方法将会由另一篇文章来介绍。其实看名字也能猜个大概,就是如何保证该数据在多个地方使用,而只需要调用一次。