[英]Typescript intellisense cannot infer generic type of conditional types based on condition
我已经设置了一个接口,它接受一个扩展联合的泛型。 在那个泛型上,我创建了一组条件类型。
type SessionContentType = "a" | "b" | "c";
interface SessionDetails<T extends SessionContentType> {
id: string;
sectionId?: string;
segments?: string[];
content: {
type: T;
contentId?: T extends Exclude<SessionContentType, 'b'> ? string : never;
channelId?: T extends Exclude<SessionContentType, 'a'> ? string : never;
suffix?: T extends Exclude<SessionContentType, 'a' | 'c'> ? string : never;
}
}
到这里,似乎一切都很好。 SessionDetails["content"]["suffix"]
具有以下类型:
因此,泛型必须是'b'
类型,后缀不能是never
。
然后我创建了一个函数,该函数使用符合SessionDetails
且具有已知T
。
function getSuffixFromSession(
session: SessionDetails<Exclude<SessionContentType, 'a'>>
) {
if (session.content.type === 'c') {
session.content.type; // This is correctly of type 'c'
session.content.suffix; // I expect this to be of type 'never' but it is `string | undefined`
return '';
}
const suffix = session.content.suffix ?? '';
return suffix && `-${suffix}`;
}
为了确认我做的一切都是正确的,我添加了这两个详细的语句来查看它们的类型。
session.content.type
正如预期的那样,属于'c'
类型。 但是如果type
是c
类型(因此这应该可以写为SessionDetails<'c'>
),为什么session.content.suffix
解析为类型string | undefined
string | undefined
而不是never
(或never | undefined
,实际上?)?
如果我做
function getSuffixFromSession(
session: SessionDetails<'c'>
) {
if (session.content.type === 'c') {
session.content.type; // This is correctly of type 'c'
session.content.suffix; // I expect this to be of type 'never' but it is `undefined`
return '';
}
const suffix = session.content.suffix ?? '';
return suffix && `-${suffix}`;
}
session.content.suffix
是undefined
而不是never
,但这仍然不是我想要的。
我做错了什么或者我假设有什么问题吗?
谢谢你。
这里有几件事情正在发生。
一是您的SessionDetails<T>
类型实际上并没有像您认为的那样限制事物。 当T
是像"b" | "c"
这样的联合时"b" | "c"
"b" | "c"
, SessionDetails<T>["content"]
类型是单个对象类型而不是联合:
type Content = SessionDetails<Exclude<SessionContentType, 'a'>>["content"];
/* type Content = {
type: "b" | "c";
contentId?: string;
channelId?: string;
suffix?: string;
} */
这意味着编译器可以接受:
const sd: SessionDetails<"b" | "c"> = {
id: "",
content: {
type: "c",
suffix: "oopsie" // <-- definitely a string
}
}; // no error
getSuffixFromSession(sd);
由于当T
为"b" | "c"
时, content.suffix
的条件类型评估为string
"b" | "c"
"b" | "c"
,那么即使你在getSuffixFromSession()
的实现中验证session.content.type
是"c"
, suffix
的类型也不依赖于它, 控制流分析什么也不做。
与undefined
和never
是,当您读取碰巧丢失的可选属性时,您将获得undefined
值:
interface Foo { bar?: never };
const foo: Foo = {}
console.log(foo.bar) // undefined
never
类型表示不可能的条件; 没有类型为never
值,如果您在代码中到达编译器认为值类型为never
,那么要么编译器出错,要么在运行时永远不会到达该位置。
当您将可选属性声明为never
类型时,您实际上是在说您希望该属性根本不存在。 将undefined
分配给这样的属性有一些细微差别; 当前允许使用--strictNullChecks
但 TS4.4 将引入一个--exactOptionalPropertyTypes
编译器标志来阻止它。 但是无论哪种方式,当您读取类型为never
的可选属性时,您肯定会得到一个undefined
。
因此,接收undefined
是期望的行为。
无论如何,在 TypeScript 中获得这种“检查对象的一个属性以缩小整个对象的类型”功能的唯一方法是使用可区分的 union 。 如果你希望你content
属性是工会,而不是一个单一的对象类型,则需要发布在任何联盟,物业类型T
。 分布式条件类型将自动执行此操作:
interface SessionDetails<T extends SessionContentType> {
id: string;
sectionId?: string;
segments?: string[];
content: T extends any ? { // <-- distributes
type: T;
contentId?: T extends Exclude<SessionContentType, 'b'> ? string : never;
channelId?: T extends Exclude<SessionContentType, 'a'> ? string : never;
suffix?: T extends Exclude<SessionContentType, 'a' | 'c'> ? string : never;
} : never
}
您可以验证:
type Content = SessionDetails<Exclude<SessionContentType, 'a'>>["content"];
/* type Content = {
type: "b";
contentId?: undefined;
channelId?: string | undefined;
suffix?: string | undefined;
} | {
type: "c";
contentId?: string | undefined;
channelId?: string | undefined;
suffix?: undefined;
} */
因此以下分配失败:
const sd: SessionDetails<"b" | "c"> = {
id: "",
content: { // error!
//~~~~~~~ <-- Types of property 'suffix' are incompatible.
type: "c",
suffix: "oopsie"
}
}
getSuffixFromSession()
实现中的控制流分析按需要进行:
function getSuffixFromSession(
session: SessionDetails<Exclude<SessionContentType, 'a'>>
) {
if (session.content.type === 'c') {
session.content.type;
session.content.suffix; // undefined
return '';
}
}
您几乎是正确的,您只想将string
交换为T
。
像这样:
type SessionContentType = "a" | "b" | "c";
interface SessionDetails<T extends SessionContentType> {
id: string;
sectionId?: string;
segments?: string[];
content: {
type: T;
contentId?: T extends Exclude<SessionContentType, 'b'> ? T : never;
channelId?: T extends Exclude<SessionContentType, 'a'> ? T : never;
suffix?: T extends Exclude<SessionContentType, 'a' | 'c'> ? T : never;
}
}
function getSuffixFromSession(
session: SessionDetails<Exclude<SessionContentType, 'a'>>
) {
if (session.content.type === 'c') {
session.content.type; // => 'c'
session.content.suffix; // => 'b'
return '';
}
const suffix = session.content.suffix ?? '';
return suffix && `-${suffix}`;
}
由于 never 不会直接显示,您可以将never
替换为number
,您可以看到以下内容
我还没有深入研究 TypeScript 编译器源代码,但我猜测 typescript 将T
推断为'b' | 'c'
'b' | 'c'
在这里,所以session
被推断为SessionDetails<'b' | 'c'>
SessionDetails<'b' | 'c'>
,最终session.content.suffix
将被推断为string | never | undefined
string | never | undefined
如果你真的想要这样的 impl,也许这是一个更好的方法,明确告诉 TypeScript session
是一个SessionDetail<'c'>
(session as SessionDetails<'c'>).content.suffix; // (property) suffix?: never | undefined
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.