![](/img/trans.png)
[英]Infer generic type argument from parameter type of function in 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
分配兼容,而不是它必须(特别是) AMessage
或BMessage
。 类型{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;
可以解决单个消息中的重复type
和Message
类型联合:
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.