尽管 IDE 正确推断类型,但为什么 Typescript 不推断泛型类型保护的类型?
Why does Typescript not infer the type for a generic type guard although the IDE infers the type correctly?
Hello, world!
我正在使用 Typescript 和 Webstorm,我正在尝试使用类型保护,这似乎并不完全正确。
这是一个展示我想要实现的目标的例子。也是linked in a playground.
// This is a helper to find the matching class Type
interface correspondsTo<Klass> {}
// An interface for a plain javascript object
interface WizardPojo extends correspondsTo<RealWizard>{
name?: string;
}
// A derivable abstract class
abstract class Action {
performAction() {
console.log('Go!');
}
}
// A class which implements the plain interface and also inherits a method
class RealWizard extends Action implements WizardPojo {
name: string;
performMagic() {
console.log('✨');
}
}
// This is handled by typescript as expected
function lameMagic () {
const harry: RealWizard = new RealWizard();
harry.performMagic(); // correct
harry.performAction(); // correct
const harryPojo: WizardPojo = {};
harryPojo.performMagic() // error: 'performMagic' does not exist -> correct
harryPojo.performAction() // error: 'performAction' does not exist -> correct
}
// Now here is something not working as expected
function realMagic(maybe: WizardPojo | RealWizard ) {
if(isKlassObject(maybe)) {
// Why is that an error?
maybe.performMagic();
maybe.performAction();
const wizard: RealWizard = maybe;
// Why does not the type guard handle the cast?
const asWizard = maybe as RealWizard;
asWizard.performMagic();
asWizard.performAction()
}
}
/*
I guess this type guard needs some more magic to work correctly
The weird thing: Webstorm (without Typescript language service setting set)
recognizes the type correctly
*/
function isKlassObject<Pojo extends correspondsTo<any>> (obj: Pojo)
: obj is Pojo extends correspondsTo<infer Klass> ? Klass : unknown {
return typeof (obj as any).save === 'function';
}
问题是:当我在没有激活 'Typescript language service' 的情况下使用 Webstorm 时,类型保护会生成正确的类型。但是,当我尝试转换代码时,tsc
抛出一个错误,我不知道为什么。
在我的项目中,我有很多 Pojo 接口和 类,它们在 d.ts
文件中自动生成,我只想使用一个通用类型保护函数来确定 Pojo 是否是类对象。我想避免使用 as
.
进行显式类型转换
同时使用 Pojo 接口和 Klass 类型对我来说很重要。
你知道如何解决这个类型保护问题吗?
我找到了解决方案 !
我可以使用与 sequelize
相同的技术,在 model attribute types 的类型中使用它。
诀窍是将通用 Klass
类型分配给 correspondsTo
接口中的假 属性。
// This is a helper to find the matching class Type
interface correspondsTo<Klass> {
/*
* A dummy variable that doesn't exist on the real object.
* This is needed so Typescript can infer the type correctly in the type guard.
*/
readonly _realKlass?: Klass;
}
// Use the dummy `_realKlass` attribute to infer the correct type
function isKlassObject<Pojo> (obj: Pojo)
: obj is Pojo extends correspondsTo<any> ? Pojo['_realKlass'] : Pojo {
return typeof (obj as any).performAction === 'function';
}
// An interface for a plain javascript object
interface WizardPojo extends correspondsTo<RealWizard>{
name?: string;
}
// A derivable abstract class
abstract class Action {
performAction() {
console.log('Go!');
}
}
// A class which implements the plain interface and also inherits a method
class RealWizard extends Action implements WizardPojo {
name: string;
performMagic() {
console.log('✨');
}
}
// both type signatures are ok: either only `WizardPojo`
function pojoMagic(maybe: WizardPojo ) {
if(isKlassObject(maybe)) {
// The type is inferred correctly!
maybe.performMagic();
maybe.performAction();
}
}
// ... or the intersection type `WizardPojo | RealWizard`
function realMagic(maybe: WizardPojo | RealWizard ) {
if(isKlassObject(maybe)) {
// The type is inferred correctly!
maybe.performMagic();
maybe.performAction();
}
}
Hello, world!
我正在使用 Typescript 和 Webstorm,我正在尝试使用类型保护,这似乎并不完全正确。
这是一个展示我想要实现的目标的例子。也是linked in a playground.
// This is a helper to find the matching class Type
interface correspondsTo<Klass> {}
// An interface for a plain javascript object
interface WizardPojo extends correspondsTo<RealWizard>{
name?: string;
}
// A derivable abstract class
abstract class Action {
performAction() {
console.log('Go!');
}
}
// A class which implements the plain interface and also inherits a method
class RealWizard extends Action implements WizardPojo {
name: string;
performMagic() {
console.log('✨');
}
}
// This is handled by typescript as expected
function lameMagic () {
const harry: RealWizard = new RealWizard();
harry.performMagic(); // correct
harry.performAction(); // correct
const harryPojo: WizardPojo = {};
harryPojo.performMagic() // error: 'performMagic' does not exist -> correct
harryPojo.performAction() // error: 'performAction' does not exist -> correct
}
// Now here is something not working as expected
function realMagic(maybe: WizardPojo | RealWizard ) {
if(isKlassObject(maybe)) {
// Why is that an error?
maybe.performMagic();
maybe.performAction();
const wizard: RealWizard = maybe;
// Why does not the type guard handle the cast?
const asWizard = maybe as RealWizard;
asWizard.performMagic();
asWizard.performAction()
}
}
/*
I guess this type guard needs some more magic to work correctly
The weird thing: Webstorm (without Typescript language service setting set)
recognizes the type correctly
*/
function isKlassObject<Pojo extends correspondsTo<any>> (obj: Pojo)
: obj is Pojo extends correspondsTo<infer Klass> ? Klass : unknown {
return typeof (obj as any).save === 'function';
}
问题是:当我在没有激活 'Typescript language service' 的情况下使用 Webstorm 时,类型保护会生成正确的类型。但是,当我尝试转换代码时,tsc
抛出一个错误,我不知道为什么。
在我的项目中,我有很多 Pojo 接口和 类,它们在 d.ts
文件中自动生成,我只想使用一个通用类型保护函数来确定 Pojo 是否是类对象。我想避免使用 as
.
同时使用 Pojo 接口和 Klass 类型对我来说很重要。
你知道如何解决这个类型保护问题吗?
我找到了解决方案 !
我可以使用与 sequelize
相同的技术,在 model attribute types 的类型中使用它。
诀窍是将通用 Klass
类型分配给 correspondsTo
接口中的假 属性。
// This is a helper to find the matching class Type
interface correspondsTo<Klass> {
/*
* A dummy variable that doesn't exist on the real object.
* This is needed so Typescript can infer the type correctly in the type guard.
*/
readonly _realKlass?: Klass;
}
// Use the dummy `_realKlass` attribute to infer the correct type
function isKlassObject<Pojo> (obj: Pojo)
: obj is Pojo extends correspondsTo<any> ? Pojo['_realKlass'] : Pojo {
return typeof (obj as any).performAction === 'function';
}
// An interface for a plain javascript object
interface WizardPojo extends correspondsTo<RealWizard>{
name?: string;
}
// A derivable abstract class
abstract class Action {
performAction() {
console.log('Go!');
}
}
// A class which implements the plain interface and also inherits a method
class RealWizard extends Action implements WizardPojo {
name: string;
performMagic() {
console.log('✨');
}
}
// both type signatures are ok: either only `WizardPojo`
function pojoMagic(maybe: WizardPojo ) {
if(isKlassObject(maybe)) {
// The type is inferred correctly!
maybe.performMagic();
maybe.performAction();
}
}
// ... or the intersection type `WizardPojo | RealWizard`
function realMagic(maybe: WizardPojo | RealWizard ) {
if(isKlassObject(maybe)) {
// The type is inferred correctly!
maybe.performMagic();
maybe.performAction();
}
}