簡體   English   中英

TypeScript 推斷從不鍵入但需要賦值

[英]TypeScript infers never type but requires assignment

在我的項目中,我有一個類作為文件的通用類型。 根據我們處理的文件類型,它應該公開其他屬性。

我嘗試使用默認為never “隱藏”屬性的條件類型來實現這一點。 但是,當我嘗試使用該類時,類型檢查器抱怨我缺少已推斷為類型never的屬性。 當然,我不能分配它,所以我留下了一個無法創建的對象。

該錯誤一直發生在此代碼塊的底部:

// just for convenience
type MP4OptionsT = {
    codec?: 'h264',
    profile: 'baseline' | 'main' | 'high',
    bitrate: number,
};

// this is the class in question
class MediaFile<Format extends 'mp4' | 'png'> {
    public path: string;
    public format: Format extends 'mp4' ? 'mp4' : Format extends 'png' ? 'png' : never;    // once the generic type argument is set, this can only be a specific string literal

    // this should not have to be assigned if generic type argument is 'png'
    public mp4Options: Format extends 'mp4' ? MP4OptionsT : never;

    constructor(opts: {
        path: string,
        format: Format extends 'mp4' ? 'mp4' : Format extends 'png' ? 'png' : never;
        // this should not have to be assigned if generic type argument is 'png' - however it demands to be assigned
        mp4Options: Format extends 'mp4' ? MP4OptionsT : never,
    }) {
        this.path = opts.path;
        this.format = opts.format;
        this.mp4Options = opts.mp4Options;
    }
}

// this is OK
const mp4File = new MediaFile<'mp4'>({
    path: '/some/file/somewhere.mp4',
    format: 'mp4',
    mp4Options: {
        profile: 'high',
        bitrate: 1000,
    }
});

// the type checker complains about this: "Property mp4Otions is missing in type {...}".
// if I explicitly include mp4Options, the type checker notes that "Type any is not assignable to Type never" - which makes sense, but precludes this class from ever being instantiated.
const pngFile = new MediaFile<'png'>({
    path: '/some/file/somewhere.png',
    format: 'png',    // since there is exactly one option for this, it would be nice if it were implicitly set...
});

根據我對本頁http://www.typescriptlang.org/docs/handbook/advanced-types.html部分條件類型的理解,一旦 mp4Options 被評估為,它似乎應該能夠“不存在”是類型never 作為 ab 實驗,我也嘗試讓它回退到 undefined。 如果我手動分配mp4Options: undefined ,這會起作用,否則類型檢查器仍然抱怨缺少屬性。 我認為絕對不應該是這種情況,因為我們可以省略開箱即用的undefined屬性(沒有條件類型)。

是否有解決方法或不那么復雜的方法來做到這一點? 或者我只是在我的代碼中有錯誤?

我認為通過為MediaFile使用公共基類並為mp4png格式派生兩個單獨的類可能會更好地為您服務。

如果您確實想使用條件魔法路線沿着單類走下去,我們可以做到。 雖然條件類型不能像你想要的那樣影響屬性的可選性,但我們可以將它們與交集類型結合起來,以獲得想要的效果:

// just for convenience
type MP4OptionsT = {
    codec?: 'h264',
    profile: 'baseline' | 'main' | 'high',
    bitrate: number,
};
type FormatOptions<F extends 'mp4' | 'png'> = (F extends 'mp4' ? { mp4Options: MP4OptionsT } : { mp4Options?: never})

class MediaFile<Format extends 'mp4' | 'png'> {
    public path: string;
    public format: Format // no need for a conditional type here, it the same type as Format

    public mp4Options: FormatOptions<Format>['mp4Options'];

    constructor(opts: {
        path: string,
        format: Format,
    } &  FormatOptions<Format>)
    {
        this.path = opts.path;
        this.format = opts.format;
        this.mp4Options = opts.mp4Options;
    }
}

// this is OK, no need for explicit type arguments
const mp4File = new MediaFile({
    path: '/some/file/somewhere.mp4',
    format: 'mp4',
    mp4Options: {
        profile: 'high',
        bitrate: 1000,
    }
});
mp4File.mp4Options.bitrate // ok 

// no need for the type argument 
const pngFile = new MediaFile({
    path: '/some/file/somewhere.png',
    format: 'png', // no need for mp4Options
});
pngFile.mp4Options.codec // error

這不是對我的問題的直接回答,而是嘗試編寫更具可讀性的解決方案。 Titian Cernicova-Dragomir 已經提供了一個很好的例子來說明如何做我最初要求的事情。

在玩了更多之后,我想出了這個解決方案,它避免了我在原始問題中詢問的復雜類型推斷:

type LegalFormatT = 'mp4' | 'png' | 'jpg';

type FormatOptions<F extends LegalFormatT> = F extends 'mp4' ? { options: MP4OptionsT } : F extends 'png' ? { options: PNGOptionsT } : { options?: never };

type MP4OptionsT = {
    codec?: 'h264',
    profile: 'baseline' | 'main' | 'high',
    bitrate: number,
};

type PNGOptionsT = {
    sequence: boolean,
};

class MediaFile<Format extends LegalFormatT> {
    public path: string;
    public format: Format;

    constructor(opts: {
        path: string,
        format: Format,
    }) {
        this.path = opts.path;
        this.format = opts.format;
    }
}

class MP4MediaFile extends MediaFile<'mp4'> {
    public options: FormatOptions<'mp4'>['options'];

    constructor(opts: {
        path: string,
        options: MP4OptionsT,
    }) {
        super({
            path: opts.path,
            format: 'mp4',
        });
        this.options = opts.options;
    }
}

class PNGMediaFile extends MediaFile<'png'> {
    public options: FormatOptions<'png'>['options'];

    constructor(opts: {
        path: string,
        options: PNGOptionsT,
    }) {
        super({
            path: opts.path,
            format: 'png',
        });
        this.options = opts.options;
    }
}

class JPGMediaFile extends MediaFile<'jpg'> {
    public options: FormatOptions<'jpg'>['options'];

    constructor(opts: {
        path: string,
    }) {
        super({
            path: opts.path,
            format: 'jpg',
        });
    }
}

雖然我真的很喜歡使用 TypeScript 提供的所有類型推斷功能,但我認為在這種情況下,最好“殺死我的寶貝”並做一些更多的手動工作,以避免讓一些未來的維護者感到恐慌。

非常感謝 Titian Cernicova-Dragomir 對實際問題的回答以及遵循“經典”擴展基類路線的動機。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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