可选泛型中的 Typescript 联合类型无法正常工作
我有一个接受可选泛型的类型。如果提供了泛型 G,则必须包含一个类型为 G 的新属性。然而,当我在函数中这样做时遇到了一个问题:
interface Message {
id: string;
message: string;
}
interface MessageWithContext<T> extends Message {
context: T;
};
// Optional generic. If generic provided,
// context must also provided.
type NotificationMessage<T = void> = T extends void
? Message
: MessageWithContext<T>;
interface Context1 {
status: 'GOOD';
}
interface Context2 {
status: 'BAD';
error: string;
}
type AllContext = Context1 | Context2;
// DOES NOT WORK. See error below.
function toMessage(context: AllContext): NotificationMessage<AllContext> {
return {
id: '123',
message: 'hello world',
context: context
};
}
游乐场链接。
我收到的错误:
Type '{ id: string; message: string; context: AllContext; }' is not assignable to type 'MessageWithContext<Context1> | MessageWithContext<Context2>'.
Type '{ id: string; message: string; context: AllContext; }' is not assignable to type 'MessageWithContext<Context2>'.
Types of property 'context' are incompatible.
Type 'AllContext' is not assignable to type 'Context2'.
Property 'error' is missing in type 'Context1' but required in type 'Context2'.(2322)
答案#1
这个 NotificationMessage 的定义:
type NotificationMessage<T = void> = T extends void
? Message
: MessageWithContext<T>;
是一种分配条件类型,因为你正在检查的类型T是一个泛型类型参数。分配条件类型会将类型操作分配到相关类型参数中的联合类型上,因此NotificationMessage<A | B | C>将被评估为NotificationMessage<A> | NotificationMessage<B> | NotificationMessage<C>:
type M = NotificationMessage<AllContext>
// type M = MessageWithContext<Context1> |
// MessageWithContext<Context2>
这意味着 NotificationMessage<AllContext> 本身是一个联合类型。虽然值 { id: '123', message: 'hello world', context: context } 可能 被视为可分配给这样的联合类型,但编译器不会尝试去验证这一点。它只会在目标类型是判别联合类型时才会进行此操作,而 NotificationMessage<AllContext> 并不是(它没有判别属性……不,子属性不算,参见 ms/TS#18758)。所以会出现错误。
如果你真的希望 NotificationMessage<AllContext> 本身是一个联合类型,我们可以研究如何修复 toMessage() 以使其能够工作。但你似乎实际上并没有尝试这样做。如果 NotificationMessage<AllContext> 是一个具有联合类型 context 属性的单一对象类型,你大概会感到满意。
这意味着你不希望 NotificationMessage<T> 在 T 中具有分配性。关闭分配性的最简单方法是将 T extends U 条件的两边用方括号括起来,形成单元素元组:
type NotificationMessage<T = void> = [T] extends [void]
? Message
: MessageWithContext<T>;
查看这个关于避免分配律的 Stack Overflow 问答,以了解为什么这有效。现在你已经有了一个非分配类型:
type M = NotificationMessage<AllContext>
// type M = MessageWithContext<AllContext>
而现在一切都正常工作了:
function toMessage(context: AllContext): NotificationMessage<AllContext> {
return {
id: '123',
message: 'hello world',
context: context
}; // okay
}
代码的 Playground 链接
