我希望我的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 })
是否存在类似的东西,还是我最好只进行文字代码生成?
答案 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
,会出现编译错误。
您还会看到烦人的部分PendingActionTypes
,FulfilledActionTypes
和RejectedActionTypes
可以移到另一个文件,该文件可以通过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;