[英]Typescript intellisense cannot infer generic type of conditional types based on condition
I've setup an interface that accepts a generic that extends a union.我已经设置了一个接口,它接受一个扩展联合的泛型。 On that generic, I created a set of conditional types.在那个泛型上,我创建了一组条件类型。
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;
}
}
Up to here, it seems everything fine.到这里,似乎一切都很好。 SessionDetails["content"]["suffix"]
has the following type: SessionDetails["content"]["suffix"]
具有以下类型:
So, generic must be of type 'b'
for suffix to not be never
.因此,泛型必须是'b'
类型,后缀不能是never
。
Then I create a function that uses a structure that complies to SessionDetails
with a known T
.然后我创建了一个函数,该函数使用符合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}`;
}
Just to confirm I was doing everything correctly, I added those two detailed statements to see their types.为了确认我做的一切都是正确的,我添加了这两个详细的语句来查看它们的类型。
session.content.type
is, as expected, of type 'c'
. session.content.type
正如预期的那样,属于'c'
类型。 But if type
is of type c
(and therefore this should be writable as SessionDetails<'c'>
), why does session.content.suffix
resolves as type string | undefined
但是如果type
是c
类型(因此这应该可以写为SessionDetails<'c'>
),为什么session.content.suffix
解析为类型string | undefined
string | undefined
instead of never
(or never | undefined
, actually?)? string | undefined
而不是never
(或never | undefined
,实际上?)?
If I do如果我做
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
is undefined
instead of never
, but still this is not what I'd like to obtain. session.content.suffix
是undefined
而不是never
,但这仍然不是我想要的。
Is there something wrong that am I doing or that probably I'm assuming?我做错了什么或者我假设有什么问题吗?
Here's the playground url. 这是游乐场网址。
Thank you.谢谢你。
Several things are going on here.这里有几件事情正在发生。
One is that your SessionDetails<T>
type does not actually constrain things the way you think it does.一是您的SessionDetails<T>
类型实际上并没有像您认为的那样限制事物。 When T
is a union like "b" | "c"
当T
是像"b" | "c"
这样的联合时"b" | "c"
"b" | "c"
, the type of SessionDetails<T>["content"]
is a single object type and not a union: "b" | "c"
, SessionDetails<T>["content"]
类型是单个对象类型而不是联合:
type Content = SessionDetails<Exclude<SessionContentType, 'a'>>["content"];
/* type Content = {
type: "b" | "c";
contentId?: string;
channelId?: string;
suffix?: string;
} */
Which means this is acceptable to the compiler:这意味着编译器可以接受:
const sd: SessionDetails<"b" | "c"> = {
id: "",
content: {
type: "c",
suffix: "oopsie" // <-- definitely a string
}
}; // no error
getSuffixFromSession(sd);
Since your conditional type for content.suffix
evaluates to string
when T
is "b" | "c"
由于当T
为"b" | "c"
时, content.suffix
的条件类型评估为string
"b" | "c"
"b" | "c"
, then even if you verify that session.content.type
is "c"
inside the implementation of getSuffixFromSession()
, the type of suffix
does not depend on it, and control flow analysis does nothing. "b" | "c"
,那么即使你在getSuffixFromSession()
的实现中验证session.content.type
是"c"
, suffix
的类型也不依赖于它, 控制流分析什么也不做。
The issue having to do with undefined
versus never
is that when you read an optional property that happens to be missing, you will get a value of undefined
:与undefined
和never
是,当您读取碰巧丢失的可选属性时,您将获得undefined
值:
interface Foo { bar?: never };
const foo: Foo = {}
console.log(foo.bar) // undefined
The never
type represents an impossible condition; never
类型表示不可能的条件; there are no values of type never
, and if you get to a place in your code where the compiler thinks a value is of type never
, then either the compiler is mistaken, or that place will never be reached at runtime.没有类型为never
值,如果您在代码中到达编译器认为值类型为never
,那么要么编译器出错,要么在运行时永远不会到达该位置。
When you have an optional property declared to be of type never
, you are essentially saying that you expect the property not to exist at all.当您将可选属性声明为never
类型时,您实际上是在说您希望该属性根本不存在。 There are some nuances around assigning undefined
to such a property;将undefined
分配给这样的属性有一些细微差别; it's currently allowed with --strictNullChecks
but TS4.4 will introduce an --exactOptionalPropertyTypes
compiler flag that will prevent it.当前允许使用--strictNullChecks
但 TS4.4 将引入一个--exactOptionalPropertyTypes
编译器标志来阻止它。 But either way, when you read an optional property of type never
, you are definitely going to get an undefined
.但是无论哪种方式,当您读取类型为never
的可选属性时,您肯定会得到一个undefined
。
So, receiving undefined
is desired behavior.因此,接收undefined
是期望的行为。
Anyway, the only way to get this kind of "check one property of an object to narrow the type of the whole object" functionality in TypeScript is to use a discriminated union .无论如何,在 TypeScript 中获得这种“检查对象的一个属性以缩小整个对象的类型”功能的唯一方法是使用可区分的 union 。 If you want your content
property to be a union and not a single object type, you need to distribute that property type across any union in T
.如果你希望你content
属性是工会,而不是一个单一的对象类型,则需要发布在任何联盟,物业类型T
。 A distributive conditional type will do this automatically:分布式条件类型将自动执行此操作:
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
}
Which you can verify:您可以验证:
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;
} */
And thus the following assignment fails:因此以下分配失败:
const sd: SessionDetails<"b" | "c"> = {
id: "",
content: { // error!
//~~~~~~~ <-- Types of property 'suffix' are incompatible.
type: "c",
suffix: "oopsie"
}
}
And control flow analysis inside the implementation of getSuffixFromSession()
proceeds as desired: getSuffixFromSession()
实现中的控制流分析按需要进行:
function getSuffixFromSession(
session: SessionDetails<Exclude<SessionContentType, 'a'>>
) {
if (session.content.type === 'c') {
session.content.type;
session.content.suffix; // undefined
return '';
}
}
You're almost correct, you just want to swap string
for T
.您几乎是正确的,您只想将string
交换为T
。
Like this:像这样:
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}`;
}
Since never would not display directly, you can replace the never
with number
, and you can see the following由于 never 不会直接显示,您可以将never
替换为number
,您可以看到以下内容
I haven't dived into the TypeScript compiler source code, but I'm guessing that typescript is inferring T
as 'b' | 'c'
我还没有深入研究 TypeScript 编译器源代码,但我猜测 typescript 将T
推断为'b' | 'c'
'b' | 'c'
here, so the session
is inferred to SessionDetails<'b' | 'c'>
'b' | 'c'
在这里,所以session
被推断为SessionDetails<'b' | 'c'>
SessionDetails<'b' | 'c'>
, eventually the session.content.suffix
will be inferred to string | never | undefined
SessionDetails<'b' | 'c'>
,最终session.content.suffix
将被推断为string | never | undefined
string | never | undefined
If you really want impl like this, maybe this is a better way, explicitly tell the TypeScript session
is a SessionDetail<'c'>
如果你真的想要这样的 impl,也许这是一个更好的方法,明确告诉 TypeScript session
是一个SessionDetail<'c'>
(session as SessionDetails<'c'>).content.suffix; // (property) suffix?: never | undefined
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.