在 TypeScript 中使用索引签名时如何将值类型与键匹配?
How to match value types to keys when using index signatures in TypeScript?
我在库中有一个代码用于执行某种事件源。
它定义了具有给定类型的事件和每种事件的数据。
我想编写一个 reduce 函数来遍历事件列表并将它们应用于给定的初始值。
我的问题是如何定义 reducer 函数 eventData
参数的类型以匹配当前键?我的目标是让 TypeScript 根据对象键推断参数类型,这样我就不必对事件数据参数进行类型转换(即:(eventData as AddEvent['data'])
)。
简而言之:目前 wrongReducer
对象会导致 TypeScript 错误,但我想定义 Reducer
类型以使其以这种方式工作。
是否有任何 TypeScript 魔法可以实现这一点?
// Library code:
export interface Event<
Type extends string = string,
Data extends Record<string, unknown> = Record<string, unknown>
> {
type: Type
data: Data
}
type Reducer<T, E extends Event> = {
[k in E['type']]: (current: T, eventData: E['data']) => T
// What to write instead of E['data']?
}
// Example code:
type AddEvent = Event<'add', { addend: number }>
type SubstractEvent = Event<'substract', { subtrahend: number }>
type MultiplyEvent = Event<'multiply', { multiplicant: number }>
type OperatorEvent = AddEvent | SubstractEvent | MultiplyEvent
const reducer: Reducer<number, OperatorEvent> = {
add: (current, eventData) => current + (eventData as AddEvent['data']).addend,
substract: (current, eventData) => current - (eventData as SubstractEvent['data']).subtrahend,
multiply: (current, eventData) => current * (eventData as MultiplyEvent['data']).multiplicant
}
/*
const wrongReducer: Reducer<number, OperatorEvent> = {
add: (current, eventData) => current + eventData.addend,
substract: (current, eventData) => current - eventData.subtrahend,
multiply: (current, eventData) => current * eventData.multiplicant
}
// TSError: ⨯ Unable to compile TypeScript:
// test/so.ts:32:54 - error TS2339: Property 'addend' does not exist on type '{ addend: number; } | { subtrahend: number; } | { multiplicant: number; }'.
// Property 'addend' does not exist on type '{ subtrahend: number; }'.
// 32 add: (current, eventData) => current + eventData.addend,
// ~~~~~~
// test/so.ts:33:60 - error TS2339: Property 'subtrahend' does not exist on type '{ addend: number; } | { subtrahend: number; } | { multiplicant: number; }'.
// Property 'subtrahend' does not exist on type '{ addend: number; }'.
// 33 substract: (current, eventData) => current - eventData.subtrahend,
// ~~~~~~~~~~
// test/so.ts:34:59 - error TS2339: Property 'multiplicant' does not exist on type '{ addend: number; } | { subtrahend: number; } | { multiplicant: number; }'.
// Property 'multiplicant' does not exist on type '{ addend: number; }'.
// 34 multiply: (current, eventData) => current * eventData.multiplicant
// ~~~~~~~~~~~~
*/
const events: OperatorEvent[] = [
{ type: 'add', data: { addend: 2 } },
{ type: 'multiply', data: { multiplicant: 5 } },
{ type: 'add', data: { addend: 3 } },
{ type: 'substract', data: { subtrahend: 4 } }
]
let val = 0
for (const event of events) {
val = reducer[event.type](val, event.data)
}
console.log(`Result: ${val}`)
// Result: 9
我使用 key remapping:
的一些创意版本来实现它
type Reducer<T, E extends Event> = {
[event in E as event['type']]: (current: T, eventData: event['data']) => T;
}
基本上 event
指的是 E
可以是什么,对于 Reducer<any, OperatorEvent>
是 AddEvent
、SubstractEvent
或 MultiplyEvent
。
reducer
/wrongReducer
字段中的 eventData
参数已根据字段名称正确键入。例如。 eventData
对于 add
是类型:
以更经典的方式,OperatorEvent
通常会被替换为“事件地图”或类似的东西,这更容易和更直接地覆盖地图类型。
我在库中有一个代码用于执行某种事件源。 它定义了具有给定类型的事件和每种事件的数据。
我想编写一个 reduce 函数来遍历事件列表并将它们应用于给定的初始值。
我的问题是如何定义 reducer 函数 eventData
参数的类型以匹配当前键?我的目标是让 TypeScript 根据对象键推断参数类型,这样我就不必对事件数据参数进行类型转换(即:(eventData as AddEvent['data'])
)。
简而言之:目前 wrongReducer
对象会导致 TypeScript 错误,但我想定义 Reducer
类型以使其以这种方式工作。
是否有任何 TypeScript 魔法可以实现这一点?
// Library code:
export interface Event<
Type extends string = string,
Data extends Record<string, unknown> = Record<string, unknown>
> {
type: Type
data: Data
}
type Reducer<T, E extends Event> = {
[k in E['type']]: (current: T, eventData: E['data']) => T
// What to write instead of E['data']?
}
// Example code:
type AddEvent = Event<'add', { addend: number }>
type SubstractEvent = Event<'substract', { subtrahend: number }>
type MultiplyEvent = Event<'multiply', { multiplicant: number }>
type OperatorEvent = AddEvent | SubstractEvent | MultiplyEvent
const reducer: Reducer<number, OperatorEvent> = {
add: (current, eventData) => current + (eventData as AddEvent['data']).addend,
substract: (current, eventData) => current - (eventData as SubstractEvent['data']).subtrahend,
multiply: (current, eventData) => current * (eventData as MultiplyEvent['data']).multiplicant
}
/*
const wrongReducer: Reducer<number, OperatorEvent> = {
add: (current, eventData) => current + eventData.addend,
substract: (current, eventData) => current - eventData.subtrahend,
multiply: (current, eventData) => current * eventData.multiplicant
}
// TSError: ⨯ Unable to compile TypeScript:
// test/so.ts:32:54 - error TS2339: Property 'addend' does not exist on type '{ addend: number; } | { subtrahend: number; } | { multiplicant: number; }'.
// Property 'addend' does not exist on type '{ subtrahend: number; }'.
// 32 add: (current, eventData) => current + eventData.addend,
// ~~~~~~
// test/so.ts:33:60 - error TS2339: Property 'subtrahend' does not exist on type '{ addend: number; } | { subtrahend: number; } | { multiplicant: number; }'.
// Property 'subtrahend' does not exist on type '{ addend: number; }'.
// 33 substract: (current, eventData) => current - eventData.subtrahend,
// ~~~~~~~~~~
// test/so.ts:34:59 - error TS2339: Property 'multiplicant' does not exist on type '{ addend: number; } | { subtrahend: number; } | { multiplicant: number; }'.
// Property 'multiplicant' does not exist on type '{ addend: number; }'.
// 34 multiply: (current, eventData) => current * eventData.multiplicant
// ~~~~~~~~~~~~
*/
const events: OperatorEvent[] = [
{ type: 'add', data: { addend: 2 } },
{ type: 'multiply', data: { multiplicant: 5 } },
{ type: 'add', data: { addend: 3 } },
{ type: 'substract', data: { subtrahend: 4 } }
]
let val = 0
for (const event of events) {
val = reducer[event.type](val, event.data)
}
console.log(`Result: ${val}`)
// Result: 9
我使用 key remapping:
的一些创意版本来实现它type Reducer<T, E extends Event> = {
[event in E as event['type']]: (current: T, eventData: event['data']) => T;
}
基本上 event
指的是 E
可以是什么,对于 Reducer<any, OperatorEvent>
是 AddEvent
、SubstractEvent
或 MultiplyEvent
。
reducer
/wrongReducer
字段中的 eventData
参数已根据字段名称正确键入。例如。 eventData
对于 add
是类型:
以更经典的方式,OperatorEvent
通常会被替换为“事件地图”或类似的东西,这更容易和更直接地覆盖地图类型。