打字稿通用通用

时间:2021-02-13 03:07:04

标签: javascript typescript generics

不确定我想要什么打字稿功能,但我想我需要一个通用的泛型(如果那是打字稿功能)。以下是我的要求。

我想表示一组事件/事件处理程序元组。以下是我目前所拥有的。

interface Event {
 type: string;
}

type EventType<T extends Event> = T['type'];
type EventHandler<T extends Event> = (event:T): void;

// so far so good
type EventTypeAndHandlerTuple <T extends Event> = [EventType<T>, EventHandler<T>];


// this is where I wish I could do better. I am forced to used any
type EventTypeAndHandlerTuples = EventTypeAndHandlerTuple<any>[]

我希望能够表示这样的代码

const todoAddedEvent: TodoAddedEvent = {...}
const handleTodoAddedEvent: TodoAddedHandler = (event: TodoAddedEvent) => {...};
const todoRemovedEvent: TodoRemovedEvent = {...}
const handleTodoRemovedEvent: TodoRemovedHandler = (event: TodoRemovedEvent) => {...}

// HELP ME GIVE THIS THING A TYPE ?
const eventTypeToHandlerTuples = [
 [todoAddedEvent, handleTodoAddedEvent],
 [todoRemovedEvent, handleTodoRemovedEvent]
];

我想要一个 eventTypeToHandlerTuples 类型,如果它是关联事件类型和事件处理程序元组的集合,它就会通过。如果事件类型与不正确的处理程序相关联,我希望这种类型表示失败。例如,我希望这个失败

// this should fail because the removed event is being paired with the add event handler.
const eventTypeToHandlerTuples = [
 [todoRemovedEvent, handleTodoAddedEvent], 
];

干杯并提前感谢您的意见

1 个答案:

答案 0 :(得分:3)

最简单的方法是收集您关心的所有 Event 类型的联合;这可能是可能的,因为您显然希望它们的 type 属性是唯一的 string literal types,可用于 discriminate such a union。如果是这样,您定义联合:

type Events = TodoAddedEvent | TodoRemovedEvent;

然后使用它来描述EventTypeAndHandlerTuple<T>联合中每个元素T的所有可能的Events类型的联合,使用一些联合构建技术,如distributive conditional types:< /p>

type UnionOfEventTypeAndHandlerTuples = Events extends infer T ?
  T extends Event ? EventTypeAndHandlerTuple<T> : never : never;
// type UnionOfEventTypeAndHandlerTuples = 
// EventTypeAndHandlerTuple<TodoAddedEvent> | EventTypeAndHandlerTuple<TodoRemovedEvent>

一旦你有了这个,你想要的类型就是UnionOfEventTypeAndHandlerTuples[]

// okay
const eventTypeToHandlerTuples: UnionOfEventTypeAndHandlerTuples[] = [
  [todoAddedEvent, handleTodoAddedEvent],
  [todoRemovedEvent, handleTodoRemovedEvent]
];

// error
const badEventTypeToHandlerTuples: UnionOfEventTypeAndHandlerTuples[] = [
  [todoRemovedEvent, handleTodoAddedEvent], // error!
];

如果您没有预定义的联合,那么事情会变得更加困难。如果没有类似 TypeScript 中对存在泛型 的原生支持(参见 microsoft/TypeScript#14466),就没有像 EventTypeAndHandlerTuple<exists T extends Event>[] 这样的特定类型,其中 exists T 的意思是“我不知道或者关心 T 是什么,我只关心它存在”。您可以将存在类型视为所有可能匹配类型的“无限联合”。

相反,您需要做其他事情。 TypeScript 最合理的可能是在 TypeScript 中使用常规的旧泛型类型(这些被称为 universal 而不是 existential 并且表现得像“无限交集”而不是“无限联合” "),并使用辅助函数使编译器推断成为您的泛型,这样您就不必将其写出来:

const asEventTypeToHandlerTuples = <T extends Event[]>(
  tuples: [...{ [I in keyof T]: EventTypeAndHandlerTuple<Extract<T[I], Event>> }]
) => tuples;

辅助函数 asEventTypeToHandlerTuples() 接受一个名为 tuples 的参数并返回它。此参数的类型被限制为 mapped tuple type,它将 Event 类型的数组转换为 EventTypeAndHandlerTuple 类型的相应数组。如果 T 的类型被推断为 [TodoAddedEvent, TodoRemovedEvent, SomeOtherEvent],那么 tuples() 的类型被约束为 [EventTypeAndHandlerTuple<TodoAddedEvent>, EventTypeAndHandlerTuple<TodoRemovedEvent>, EventTypeAndHandlerTuple<SomeOtherEvent>]。让我们看看它是如何工作的。我们不再注释变量的类型,而是让编译器推断它们:

// okay
const eventTypeToHandlerTuples = asEventTypeToHandlerTuples([
  [todoAddedEvent, handleTodoAddedEvent],
  [todoRemovedEvent, handleTodoRemovedEvent]
]);

// error
const badEventTypeToHandlerTuples = asEventTypeToHandlerTuples([
  [todoRemovedEvent, handleTodoAddedEvent], // error!
]);

eventTypeToHandlerTuples 赋值没有问题,但 badEventTypeToHandlerTuples 给出错误,因为它推断其类型为 [EventTypeAndHandlerTuple<TodoAddedEvent>],但 todoRemovedEvent 不能赋值给 {{1 }},你就会得到想要的错误。


就我个人而言,我会尝试使用类似 EventType<TodoAddedEvent> 的联合,因为处理特定类型的联合比将泛型类型参数与代码库一起拖动来表示约束要容易得多。但如果这不可行,通用辅助函数通常是您正在寻找的那种不可表示的存在类型的可接受替代品。

Playground link to code