首页 > 解决方案 > typescript redux 操作:组合与继承

问题描述

在与typescript 一起使用的 redux 手册中,他们将操作类型定义为:

// src/store/chat/types.ts
export const SEND_MESSAGE = 'SEND_MESSAGE'
export const DELETE_MESSAGE = 'DELETE_MESSAGE'

interface SendMessageAction {
  type: typeof SEND_MESSAGE
  payload: Message
}

interface DeleteMessageAction {
  type: typeof DELETE_MESSAGE
  meta: {
    timestamp: number
  }
}

export type ChatActionTypes = SendMessageAction | DeleteMessageAction

这很是一种类型的组合


相反,您可以这样定义它们:

BaseAction.ts:

export interface BaseAction {
  type: any;
  payload: any;
}

具体操作.ts:

export enum SpecificActionType {
  SPECIFIC_ACTION_1 = "action1",
  SPECIFIC_ACTION_2 = "action2",
}

export interface SpecifcAction extends BaseAction {
  type: SpecificActionType;
}

export const SpecificAction1 = (param: SomeType): SpecifcAction => ({
  type: SpecificActionType.SPECIFIC_ACTION_1,
  payload: {
    stream
  },
});

export const SpecificAction2 = (param: SomeType): SpecifcAction => ({
  type: SpecificActionType.SPECIFIC_ACTION_2,
  payload: {
    uid
  }
});

这第二个基于继承的选项对我来说似乎更直观,因为它允许我为我的应用程序的不同方面创建不同的操作文件 - 无需修改原始 BaseAction.ts 文件。

作为旁注,看看创建一个SpecificAction1有点像 scala 案例类的感觉 - 如果只有 typescript 有这个范式,那么payload对于 redux 对动作类型的模式匹配的智能感知和语法糖会很好。

不过,你可以在减速器上做这样的事情:

具体Reducer.ts:

export const mySpecificReducer = (
  state: MyState = initialState,
  action: SpecifcAction 
): MyState => {

  switch (action.type) {
    case SpecificActionType.SPECIFIC_ACTION_1:
      console.log("add stream action");
      return {<change state accordingly>};

    case SpecificActionType.SPECIFIC_ACTION_2:
      return {<change state accordingly>};

    default:
      return state;
  }
};

我的问题:这里有什么缺点?

  1. 看起来这更好地遵循了打开/关闭原则,尽管我们可以这样说。
  2. 它可能会增加一些复杂性,但我假设您的应用程序中有多个方面具有固有的不同操作,因此这可能有助于更好地管理所述复杂性。
  3. 最后,您可以将 ActionTypes 作为 Enums 更好地管理,如果您使用带有智能感知的 IDE,它将帮助您管理选项。

我是否在不知不觉中射中自己的脚?

标签: reactjstypescriptredux

解决方案


从概念上讲,我只想定义从每个动作类型到有效负载形状的映射。所以我决定使用可区分联合并尽可能多地删除类型系统语法。

使用这种方法可以让 Typescript 保护我们免于使用错误的有效负载编写动作创建者,或制作拼写错误。

老实说,在大型项目中使用 Redux 最困难的部分是编写所有样板文件,因此我尽量将其保持在最低限度。

export type IceCreamActionShapes = {
  type: IceCreamActionTypeKeys.SET_FLAVOR;
  payload: string;
} | {
  type: IceCreamActionTypeKeys.CREATE_FLAVOR;
  payload: IFlavor;
} | {
  type: IceCreamActionTypeKeys.MELT;
}

我不认为应用程序的任何复杂性都应该由 redux 操作调度层管理。但是,您的示例BaseAction仍然非常简单。即使您IceCreamBaseAction为每只鸭子添加了特定于域的 BaseAction,它仍然很容易维护,因为它是打开/关闭的。

我总是使用字符串枚举作为操作键。键是可重构和可搜索的,您可以向字符串值添加额外的上下文信息。


推荐阅读