繁体   English   中英

为什么带有类型联合类型参数的泛型 TypeScript 函数不能推断出与返回类型相同的类型?

[英]Why cannot a generic TypeScript function with an argument of type from a type union infer the same type as the return type?

type AMessage = { type: 'a'; optionalNumber?: number; };
type BMessage = { type: 'b'; optionalString?: string; };
type Message = AMessage | BMessage;

function exchangeMessage<T extends Message>(message: T): Required<T> {
  return null as any;
}

const message1 = exchangeMessage({ type: 'a' });

// `optionalNumber` is not accessible, `Required<{ type: "a"; }>` is inferred
message1.optionalNumber;

const message2 = exchangeMessage({ type: 'a' } as AMessage);
message2.optionalNumber;

export {};

游乐场链接

在此代码中,为什么必须显式键入参数(如message2行所示)才能使返回值成为AMessage具体类型,以及为什么没有推断出Required<AMessage>Required<{ type: "a"; }>是)作为唯一可能的类型,当不使用显式类型转换时隐式返回值(如message1行所示)?

以及如何更改此代码,以便泛型函数返回任一联合类型成员类型,具体取决于参数类型与哪些类型兼容?

这是一个推理问题。 message可以是符合{type: string}的任何类型。 编译器无法将其推断为 ONLY AMessage

看这个例子就明白了。

type AMessage = { type: 'a'; optionalNumber?: number; }
type BMessage = { type: 'b'; optionalString?: string; }
type Message = AMessage | BMessage;


async function exchangeMessage<T extends Message>(message: T): Promise<T> {
  return new Promise((resolve, reject) => { /* … */ });
}

const aMessage: AMessage = { type: 'a' };
const message = await exchangeMessage(aMessage);

message.optionalNumber; // Everything is fine

const aMessage2 = { type: 'a' } as const; // as const required to accepted as paramter 
const message2 = await exchangeMessage(aMessage2); // typed a  { readonly type: "a"; }

message2.optionalNumber; // problem

操场


编辑条件返回类型是否符合您的需求?

type AMessage = { type: 'a'; optionalNumber?: number; };
type BMessage = { type: 'b'; optionalString?: string; };
type Message = AMessage | BMessage;

declare function exchangeMessage<T extends Message>(message: T): Required<T extends AMessage ? AMessage :  BMessage>

const aMessage = exchangeMessage({ type: 'a' });
const bMessage = exchangeMessage({ type: 'b' }); 
const cMessage = exchangeMessage({ type: 'c' }); // NOPE 

aMessage.optionalNumber; // OK 
bMessage.optionalString; // OK

操场

...为什么必须显式键入参数...

因为T extends Message只是意味着T必须与Message分配兼容,而不是它必须(特别是) AMessageBMessage 类型{type: "a"}Message赋值兼容,因为它与AMessage赋值兼容。 这并不意味着它AMessage ,只是它与它兼容。

以及如何更改此代码,以便泛型函数返回任一联合类型成员类型,具体取决于参数类型与哪些类型兼容?

您可以使用函数重载而不是泛​​型来做到这一点(或者,如Matthieu Riegler 指出的那样,条件返回类型)

function exchangeMessage(message: AMessage): Required<AMessage>;
function exchangeMessage(message: BMessage): Required<BMessage>;
function exchangeMessage(message: Message): Required<Message> {
    // ...implementation...
}

游乐场链接

不使用泛型意味着您要更严格地限制函数接受的内容。 不利的一面是,如果您添加第三种消息类型,则必须添加第三种重载(或编辑您的条件返回类型,如果使用该解决方案)。

我设法提出了一个使用映射类型的解决方案:

type AMessage = { type: 'a'; optionalNumber?: number; };
type BMessage = { type: 'b'; optionalString?: string; };
type Messages = {
  a: AMessage
  b: BMessage
};

type Message = Messages[keyof Messages];

function exchangeMessage<T extends Message>(message: T): Required<Messages[T["type"]]> {
  return null as any;
}

const message = exchangeMessage({ type: 'a' });
message.optionalNumber;

游乐场链接

可以解决单个消息中的重复typeMessage类型联合:

type Messages = {
  a: { requiredNumber: number; optionalNumber?: number; }
  b: { requiredString: number; optionalString?: string; }
};

type MessagesWithTypes = { [type in keyof Messages]: { type: type } & Messages[type] };
type Message = MessagesWithTypes[keyof MessagesWithTypes];

function exchangeMessage<T extends Message>(message: T): Required<Messages[T["type"]]> {
  return null as any;
}

const message = exchangeMessage({ type: 'a', requiredNumber: 0 });
message.requiredNumber;
message.optionalNumber;

游乐场链接

它检查所有框:我不必明确指定输入消息类型,它被推断为类型联合中的类型之一,输出类型从映射类型中提取并设为Required

我相信这是条件返回类型/函数重载硬币的“第三面”。 在添加新消息类型时,所有这些解决方案都需要某种维护,但我最喜欢这个选项,因为维护量最小(映射类型的一行 - 不需要触摸函数)。

最初我试图找到一种从类型联合派生映射类型的方法,如下所示:

type Messages = { [type: typeof Message["type"]]: Message[type] }

但我不认为这是可能的。 它最终引导我进入映射类型,并意识到我也可以从映射类型派生联合,如果我不能反过来的话。

暂无
暂无

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

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