簡體   English   中英

打字稿智能感知無法根據條件推斷條件類型的通用類型

[英]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'類型。 但是如果typec類型(因此這應該可以寫為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.suffixundefined而不是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的類型也不依賴於它, 控制流分析什么也不做。


undefinednever是,當您讀取碰巧丟失的可選屬性時,您將獲得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 '';
    }
}

Playground 鏈接到代碼

您幾乎是正確的,您只想將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}`;
}

TS游樂場

由於 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM