Typescript:我可以在TS本身内动态生成类型吗?

时间:2019-04-17 13:06:08

标签: typescript redux

我希望我的redux动作类型是静态类型的,所以我可以在处理诸如减速器等动作的代码中受益于错别字检测,自动完成等。

要使其正常工作,我真的需要像这样在应用程序中静态键入每个动作的结构

type Actions =
  | { type: 'A_PENDING' }
  | { type: 'A_FULFILLED', payload: { a: number } }
  | { type: 'A_REJECTED', error: Error }
  | { type: 'B_PENDING' }
  | { type: 'B_FULFILLED', payload: { b: number } }
  | { type: 'B_REJECTED', error: Error }
  | { type: 'C_PENDING' }
  | { type: 'C_FULFILLED', payload: { c: number } }
  | { type: 'C_REJECTED', error: Error }

但是,为我的所有动作写出这些类型有很多重复之处。

我知道我可以编写编辑器模板来为我从字面上生成此代码,但是我一直想知道是否存在任何“本机” TS方式来生成此类类型定义模式。

我想像这样的语法(伪代码)

typegenerator AsyncAction = (BaseString, PayloadType) => 
  | { type: BaseString + '_PENDING' }
  | { type: BaseString + '_FULFILLED' }, payload: PayloadType }
  | { type: BaseString + '_REJECTED' }, error: Error }

type Actions =
  | AsyncAction('A', { a: number })
  | AsyncAction('B', { b: number })
  | AsyncAction('C', { c: number })

是否存在类似的东西,还是我最好只进行文字代码生成?

2 个答案:

答案 0 :(得分:1)

好吧,目前无法在类型级别上串联字符串。 (对功能here的类似请求)

但是以下内容可能会使代码的重复性降低(如果不降低重复性,至少会更加系统地重复)

type AsyncActionType = "A" | "B";
type AsyncActionPayloads = {
    "A": { a: number },
    "B": { b: string }
}

type PendingActionTypes = {
    "A": "A_PENDING",
    "B": "B_PENDING"
}

type FulfilledActionTypes = {
    "A": "A_FULFILLED",
    "B": "B_FULFILLED"
}

type RejectedActionTypes = {
    "A": "A_REJECTED",
    "B": "B_REJECTED"
}

type AsyncAction = {
    [T in AsyncActionType]: {
        type: PendingActionTypes[T]
    } | {
        type: FulfilledActionTypes[T],
        payload: AsyncActionPayloads[T]
    } | {
        type: RejectedActionTypes[T],
        error: Error
    }
}[AsyncActionType];

type Action =
    { type: "MY_OTHER_NON_ASYNC_ACTION" } |
    { type: "MY_ANOTHER_NON_ASYNC_ACTION", payload: { foo: number } } |
    AsyncAction;

Playground demo。将鼠标悬停在AsyncAction上,您会看到它是所有预期的异步动作的并集。另外,请尝试从"B": "B_PENDING"中删除PendingActionTypes,会出现编译错误。

您还会看到烦人的部分PendingActionTypesFulfilledActionTypesRejectedActionTypes可以移到另一个文件,该文件可以通过nodejs脚本生成,该脚本读取包含{{1}的文件},并在文件更改时执行

答案 1 :(得分:0)

我也想发布此答案,因为它更接近(实际上有点准确)您的伪代码。我以前没有发布此内容,因为它不会减少重复性,而只是取代它。 (也许使其更具重复性?idk)。如果我忽略extends "A" | "B",它要求保持一致性并使之重复,那么我会更喜欢这种解决方案。

type AsyncAction<T extends "A" | "B", P> = 
    | { type: AddSuffix<T, "PENDING"> }
    | { type: AddSuffix<T, "FULFILLED">, payload: P }
    | { type: AddSuffix<T, "REJECTED">, error: Error }

type Action =
    | AsyncAction<"A", { a: string }>
    | AsyncAction<"B", { b: string }>



type AddSuffix<
    T extends "A" | "B",
    S extends "PENDING" | "FULFILLED" | "REJECTED"
> =
    T extends "A" ?
        S extends "PENDING" ? "A_PENDING" :
        S extends "FULFILLED" ? "A_FULFILLED" :
        S extends "REJECTED" ? "A_REJECTED" :
        never :
    T extends "B" ?
        S extends "PENDING" ? "B_PENDING" :
        S extends "FULFILLED" ? "B_FULFILLED" :
        S extends "REJECTED" ? "B_REJECTED" :
        never : 
    never;

Playground demo