[英]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.