简体   繁体   English

在 TypeScript 中使用索引签名时如何将值类型与键匹配?

[英]How to match value types to keys when using index signatures in TypeScript?

I have a code in a library for doing some kind of eventsourcing.我在库中有一段代码用于进行某种事件溯源。 It defines events with a given type and data for each kind of events.它为每种事件定义具有给定类型和数据的事件。

I want to write a reduce function that walks through a list of events and apply them to a given initial value.我想编写一个 reduce 函数,它遍历事件列表并将它们应用于给定的初始值。

My question is how I can define the type of the reducer function eventData parameter to match the current key?我的问题是如何定义 reducer 函数eventData参数的类型以匹配当前键? My goal is to get TypeScript to infer parameter types based on the object key so I don't have to typecast the event data parameters (ie: (eventData as AddEvent['data']) ).我的目标是让 TypeScript 根据对象键推断参数类型,这样我就不必对事件数据参数进行类型转换(即: (eventData as AddEvent['data']) )。

In a nutshell: currently the wrongReducer object leads to TypeScript errors but I want to define the Reducer type to make it work this way.简而言之:目前错误的wrongReducer对象会导致TypeScript 错误,但我想定义Reducer类型以使其以这种方式工作。

Is there any TypeScript magic to achieve this?是否有任何 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


I got this to work using a bit of a creative version of key remapping :我使用一些创造性的键重新映射版本让它工作

type Reducer<T, E extends Event> = {
    [event in E as event['type']]: (current: T, eventData: event['data']) => T;
}

Basically event refers to whatever E can be, which for Reducer<any, OperatorEvent> is AddEvent , SubstractEvent or MultiplyEvent .基本上event指的是任何E可以是,对于Reducer<any, OperatorEvent>AddEventSubstractEventMultiplyEvent

The eventData parameter in your reducer / wrongReducer 's fields are properly typed based on the field name.您的reducer / wrongReducer字段中的eventData参数是根据字段名称正确输入的。 Eg eventData for add is of type:例如,用于add eventData类型为:

在此处输入图片说明


In a more classic fashion, OperatorEvent would usually be replaced with an "event map" or similar, which is a lot easier and more straight-forward to map types over.在更经典的方式中, OperatorEvent通常会被替换为“事件映射”或类似的,这样映射类型更容易、更直接。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM