TypeScript 从不推断类型但需要赋值
TypeScript infers never type but requires assignment
在我的项目中,我有一个 class 作为文件的通用类型。根据我们处理的文件类型,它应该公开其他属性。
我尝试使用默认为 never
到 "hide" 和 属性 的条件类型来实现此目的。然而,当我尝试使用 class 时,类型检查器抱怨说我遗漏了被推断为 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
类型,它似乎应该能够 "not there" .
作为 ab 实验,我还尝试让它回落到未定义状态。如果我手动分配 mp4Options: undefined
,这会起作用,否则类型检查器仍然会抱怨缺少属性。我认为这绝对不是这种情况,因为我们可以省略 undefined
开箱即用的属性(没有条件类型)。
是否有解决方法或更简单的方法来执行此操作?还是我的代码有错误?
我认为为 MediaFile
使用公共基础 class 并为 mp4
和 [=13 派生两个单独的 class 可能会更好=]格式。
如果你想走单条 class 有条件的魔法路线,我们可以做到。虽然条件类型不能像你想的那样影响 属性 的可选性,但我们可以将它们与交集类型结合起来以获得所需的效果:
// 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 必须提供的所有类型推断功能,但我认为在这种情况下它比 "kill my darling" 更可取,并且做一些更多的手动工作以避免让未来的维护者恐慌。
非常感谢 Titian Cernicova-Dragomir 对实际问题的回答以及遵循 "classic" 扩展基础 class 路线的动力。
在我的项目中,我有一个 class 作为文件的通用类型。根据我们处理的文件类型,它应该公开其他属性。
我尝试使用默认为 never
到 "hide" 和 属性 的条件类型来实现此目的。然而,当我尝试使用 class 时,类型检查器抱怨说我遗漏了被推断为 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
类型,它似乎应该能够 "not there" .
作为 ab 实验,我还尝试让它回落到未定义状态。如果我手动分配 mp4Options: undefined
,这会起作用,否则类型检查器仍然会抱怨缺少属性。我认为这绝对不是这种情况,因为我们可以省略 undefined
开箱即用的属性(没有条件类型)。
是否有解决方法或更简单的方法来执行此操作?还是我的代码有错误?
我认为为 MediaFile
使用公共基础 class 并为 mp4
和 [=13 派生两个单独的 class 可能会更好=]格式。
如果你想走单条 class 有条件的魔法路线,我们可以做到。虽然条件类型不能像你想的那样影响 属性 的可选性,但我们可以将它们与交集类型结合起来以获得所需的效果:
// 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 必须提供的所有类型推断功能,但我认为在这种情况下它比 "kill my darling" 更可取,并且做一些更多的手动工作以避免让未来的维护者恐慌。
非常感谢 Titian Cernicova-Dragomir 对实际问题的回答以及遵循 "classic" 扩展基础 class 路线的动力。