【问题标题】:remove duplication in async api call types with typescript使用打字稿删除异步 api 调用类型中的重复项
【发布时间】:2019-01-09 22:09:21
【问题描述】:

我有以下类型的联合:

export interface GetAll { type: PeopleActionTypes.GET_ALL; }
export type GetAllOk = ApiActionCreator<{ type: PeopleActionTypes.GET_ALL_OK; }>;
export type GetAllFail = ApiActionCreator<{type: PeopleActionTypes.GET_ALL_FAIL}>;
export type GetOne = ApiActionCreator<{type: PeopleActionTypes.GET_ONE}>;
export type GetOneOk = ApiActionCreator<{type: PeopleActionTypes.GET_ONE_OK}>;
export type GetOneFail = ApiActionCreator<{type: PeopleActionTypes.GET_ONE_FAIL}>;
export type Add = ApiActionCreator<{type: PeopleActionTypes.ADD}, Fetchable<Person>>;
export type AddOK = ApiActionCreator<{type: PeopleActionTypes.ADD_OK}, Fetchable<Person>[]>;
export type AddFail = ApiActionCreator<{type: PeopleActionTypes.ADD_FAIL}>;
export type Update = ApiActionCreator<{type: PeopleActionTypes.UPDATE}>;
export type UpdateOK = ApiActionCreator<{type: PeopleActionTypes.UPDATE_OK}, Fetchable<Person>[]>;
export type UpdateFail = ApiActionCreator<{type: PeopleActionTypes.UPDATE_FAIL}>;
export type Remove = ApiActionCreator<{type: PeopleActionTypes.REMOVE}>;
export type RemoveOK = ApiActionCreator<{type: PeopleActionTypes.REMOVE_OK}>;
export type RemoveFail = ApiActionCreator<{type: PeopleActionTypes.REMOVE_FAIL}>;
export type ResetPassword = ApiActionCreator<{type: PeopleActionTypes.RESET_PASSWORD}>;
export type ResetPasswordOK = ApiActionCreator<{type: PeopleActionTypes.RESET_PASSWORD_OK}>;
export type ResetPasswordFail = ApiActionCreator<{type: PeopleActionTypes.RESET_PASSWORD_FAIL}>;

export interface SetCurrent {
  type: PeopleActionTypes.SET_CURRENT;
  id: string;
};

export type PeopleActionCreators =
    SetCurrent
    | GetAll
    | GetAllOk
    | GetAllFail
    | GetOne
    | GetOneOk
    | GetOneFail
    | Add
    | AddOK
    | AddFail
    | Update
    | UpdateOK
    | UpdateFail
    | Remove
    | RemoveOK
    | RemoveFail
    | ResetPassword
    | ResetPasswordOK
    | ResetPasswordFail;

我的ApiActionCreator 看起来像这样:

export type ApiActionCreator&lt;T extends object, Payload = object | any[] | undefined&gt; = T &amp; { payload: Payload, error: ErrorMessage }

对于每个操作,我都有一个XxxXxxOK 和一个XxxFail

在打字稿中无论如何我可以以某种方式生成这些类型,而不必为永远操作创建所有 3 个?

【问题讨论】:

    标签: typescript


    【解决方案1】:

    您可以避免通过使用条件声明所有联合,条件类型分布在包含联合的类型参数上。使用这种行为,我们可以将ApiActionCreator 应用于枚举文字联合的所有成员。

    我们可以得到PeopleActionTypes 中除SET_CURRENT 之外的所有文字枚举的并集,使用Exclude 条件类型(type PeopleActionTypesKeys = Exclude&lt;PeopleActionTypes, PeopleActionTypes.SET_CURRENT&gt;)以不同的方式处理它

    剩下的唯一问题是某些操作类型的自定义有效负载。我们可以使用对象类型作为映射来保持枚举成员和有效负载类型之间的关系。

    type GetPayload<TPayloadMap, T extends PropertyKey> = TPayloadMap extends Record<T, infer U> ? U : undefined;
    
    export type StandardActions<TEnumKeys, TPayloadMap> =
        TEnumKeys extends any  ? ApiActionCreator<{type: TEnumKeys }, GetPayload<TPayloadMap, TEnumKeys>> 
        : never ;
    
    
    export interface SetCurrent {
        type: PeopleActionTypes.SET_CURRENT;
        id: string;
    };
    
    type PeopleActionTypesKeys = Exclude<PeopleActionTypes, PeopleActionTypes.SET_CURRENT>
    
    export type PeopleActionCreators = SetCurrent | StandardActions<PeopleActionTypesKeys, {
        [PeopleActionTypes.ADD]: Fetchable<Person>,
        [PeopleActionTypes.UPDATE]: Fetchable<Person>,
        [PeopleActionTypes.ADD_OK]: Fetchable<Person>,
    }>;
    

    上述解决方案的重复较少,不幸的是,如果您将鼠标悬停在 PeopleActionCreators 上,您会丢失类型别名的好听的名称:

    type PeopleActionCreators = SetCurrent | ApiActionCreator<{
        type: PeopleActionTypes.GET_ALL;
    }, undefined> | ApiActionCreator<{
        type: PeopleActionTypes.GET_ALL_OK;
    }, undefined> | ApiActionCreator<{
        type: PeopleActionTypes.GET_ALL_FAIL;
    }, undefined> | ApiActionCreator<{
        type: PeopleActionTypes.GET_ONE;
    }, undefined> | ApiActionCreator<{
        type: PeopleActionTypes.GET_ONE_OK;
    }, undefined> | ApiActionCreator<{
        type: PeopleActionTypes.GET_ONE_FAIL;
    }, undefined> | ApiActionCreator<{
        type: PeopleActionTypes.ADD;
    }, Fetchable<Person>> | ApiActionCreator<{
        type: PeopleActionTypes.ADD_OK;
    }, Fetchable<Person>> | ApiActionCreator<{
        type: PeopleActionTypes.ADD_FAIL;
    }, undefined> | ApiActionCreator<{
        type: PeopleActionTypes.UPDATE;
    }, Fetchable<Person>> | ApiActionCreator<{
        type: PeopleActionTypes.UPDATE_OK;
    }, undefined> | ApiActionCreator<{
        type: PeopleActionTypes.UPDATE_FAIL;
    }, undefined> | ApiActionCreator<{
        type: PeopleActionTypes.REMOVE;
    }, undefined> | ApiActionCreator<{
        type: PeopleActionTypes.REMOVE_OK;
    }, undefined> | ApiActionCreator<{
        type: PeopleActionTypes.REMOVE_FAIL;
    }, undefined> | ApiActionCreator<{
        type: PeopleActionTypes.RESET_PASSWORD;
    }, undefined> | ApiActionCreator<{
        type: PeopleActionTypes.RESET_PASSWORD_OK;
    }, undefined> | ApiActionCreator<{
        type: PeopleActionTypes.RESET_PASSWORD_FAIL;
    }, undefined>
    

    这样的可读性要差得多,即使它做同样的事情,代码本身也可能对其他人来说更难理解。

    【讨论】:

    • 这很棒,即使我最终没有使用它:)。很有教育意义。我没有得到的一件事是TEnumKeys extends any 作为条件。这怎么不总是正确的?
    • @dagda1 当我做这种解决方案时,这总是我得到的问题:))。这总是正确的,但我们对条件类型的条件行为不感兴趣,我们对它们的分配行为感兴趣,条件无关紧要。请参阅文档以了解分配行为 typescriptlang.org/docs/handbook/advanced-types.html 或此处的更简单解释:stackoverflow.com/questions/51651499/…
    • 我明白了,这是有道理的。这很棒。对其他人来说可能太难以阅读,但正如我所说的那样很有教育意义。
    • 这实际上是在说什么type GetPayload&lt;TPayloadMap, T extends PropertyKey&gt; = TPayloadMap extends Record&lt;T, infer U&gt; ? U : undefined; 如果TPayloadMap 是一个记录,返回传递给记录的泛型类型?
    • @dagda1 在某种程度上,一切基本上都是Record。它说如果TPayloadMap 中存在密钥T,请在U 中给我与之关联的类型。
    猜你喜欢
    • 2020-03-01
    • 2022-11-17
    • 1970-01-01
    • 2021-07-17
    • 2020-11-29
    • 1970-01-01
    • 2022-06-10
    • 2018-10-04
    • 1970-01-01
    相关资源
    最近更新 更多