繁体   English   中英

TypeScript 泛型类型推断

[英]TypeScript Generic Type Inference

我在 TypeScript 中使用隐式any实现了一个非常简单的消息传递系统,并且正在尝试正确键入它,因此不会在任何地方丢失类型信息。

这些消息是简单的对象,它们携带处理函数使用的一些信息。 所有消息都有一个message.type属性,用于决定调用哪个处理程序 function。

有一个基本接口Message ,它只定义了这个type属性,然后有从它扩展的特定接口。

我不太清楚如何正确输入,也不确定确切的问题是什么。

实际上,编译器失败并显示以下错误消息:

Type '(message: MessageA) => void' is not assignable to type 'MessageHandler'.
  Types of parameters 'message' and 'message' are incompatible.
    Type 'T' is not assignable to type 'MessageA'.
      Property 'x' is missing in type 'Message' but required in type 'MessageA'.

这是可以重现该问题的代码的简化版本:

export enum MessageType {
  MessageTypeA,
  MessageTypeB,
}

export interface Message {
  readonly type: MessageType
}

export interface MessageA extends Message {
  readonly type: MessageType.MessageTypeA
  readonly x: string
}

export interface MessageHandler {
  <T extends Message>(message: T): void
}

const onMessageA: MessageHandler = (message: MessageA) => {
  console.log(message.x)
}

我相信消息传递系统还有其他部分,但并不直接相关。

由于系统的 rest 是如何工作的,我需要 TS 来推断泛型。 例如,我不能如下声明MessageHandler

export interface MessageHandler<T extends Message> {
  (message: T): void
}

使用 TypeScript 3.8.33.9.2尝试了此代码。

这是 TypeScript Playground 中此代码的链接: link

我也尝试如下声明MessageHandler ,但得到了同样的错误:

export type MessageHandler = <T extends Message>(message: T) => void

如何正确键入MessageHandler以便它可以接受任何类型的消息,只要它具有type属性,而无需在调用时显式传递类型?

编辑

添加一些上下文,我正在使用这样的MessageHandler

const defaultFallback = <T extends Message>(message: T) => console.warn('Received message with no handler', message)


export type MessageHandlers = {
  readonly [P in MessageType]?: MessageHandler;
}

export const makeHandler = (functions: MessageHandlers, fallback: MessageHandler = defaultFallback) => (message: Message) => {
  if (!message)
    return

  const handler = functions[message.type]

  if (handler)
    handler(message)
  else if (fallback)
    fallback(message)
}

const onMessageA: MessageHandler = (message: MessageA) => {
  console.log(message.x)
}

const onMessageB: MessageHandler = (message: MessageB) => {
  ...
}

makeHandler({
  [MessageType.MessageA]: onMessageA,
  [MessageType.MessageB]: onMessageB,
})

你所要求的不是类型安全的,你最终需要很多any或其他类型的断言来让它工作。 问题是onMessageAonMessageB实际上只分别接受MessageAMessageB类型的消息; 如果您尝试将它们注释为应该“接受任何类型的消息,只要它具有type属性”的类型,您将收到编译器警告,这是正确的。 这些处理程序的正确类型是您说您不能使用的版本,其中MessageHandler<T>本身就是通用的:

export interface MessageHandler<T extends Message> {
  (message: T): void;
}

然后你可以自己注释它们:

const onMessageA: MessageHandler<MessageA> = message => {
  console.log(message.x);
};

或者您可以编写一个帮助程序 function 让编译器为您推断T

// helper function for type inference
const oneHandler = <T extends Message>(h: MessageHandler<T>) => h;

// onMessageA will be inferred as a MessageHandler<MessageA>:
const onMessageA = oneHandler((message: MessageA) => {
  console.log(message.x);
});

由于您的用例涉及构建一个处理程序,该处理程序可以真正处理来自联合中每个成员的一堆处理程序中的一些有区别的Message类型联合中的任何内容,您可以使用通用MessageHandler<T>来描述该过程。 首先,我们需要完整的可区分联合作为一种类型:

type Messages = MessageA | MessageB;

然后我们可以编写一个makeHandler() function ,它接受从MessageType到各个处理程序的映射:

function makeHandler(
  map: {
    [P in Messages["type"]]: MessageHandler<Extract<Messages, { type: P }>>
  }
): MessageHandler<Messages> {
  return <M extends Messages>(m: M) => (map[m.type] as MessageHandler<M>)(m);
}

输入类型[P in Messages["type"]]: MessageHandler<Extract<Messages, { type: P }>>等价于

{
  [MessageType.MessageTypeA]: MessageHandler<MessageA>;
  [MessageType.MessageTypeB]: MessageHandler<MessageB>;
};

这是您要传递的内容。 output 类型MessageHandler<Messages>是完整联合的处理程序。

运行时的实现看起来像m => map[m.type](m) 现在,如果编译器能够验证这是类型安全的,那就太好了,但不幸的是,它的推理顺序太高了。 编译器会将map[m.type]视为一个联合,并将m视为另一个联合,并将它们视为独立的,但当然不是。 编译器担心map[m.type]可能是MessageHandler<MessageA>mMessageB ,即使这是不可能的。 我提交了关于这个一般 class 问题的microsoft/TypeScript#30581 不幸的是,这里没有很好的答案。 通常我只是使用像map[m.type] as MessageHandler<M>并继续前进。 另一种选择是使用冗余但类型安全的实现:

return (m: Messages) => m.type === MessageType.MessageTypeA ? map[m.type](m) : map[m.type](m);

无论如何,这意味着您应该能够创建和使用完整的处理程序,而无需手动注释特定的处理程序类型,或者通过使用由oneHandler()创建的现有单个处理程序,或者通过在 object 中构建它们直接传递给fullHandler()

const fullHandler = makeHandler({
  [MessageType.MessageTypeA]: m => console.log(m.x),
  [MessageType.MessageTypeB]: m => console.log(m.y)
});

fullHandler({ type: MessageType.MessageTypeA, x: "" }); // okay
fullHandler({ type: MessageType.MessageTypeA, y: "" }); // error
fullHandler({ type: MessageType.MessageTypeB, x: "" }); // error
fullHandler({ type: MessageType.MessageTypeB, y: "" }); // okay

在我看来很好。 好的,希望有帮助; 祝你好运!

Playground 代码链接

暂无
暂无

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

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