TypeScript中如何关联类的两个字段类型实现类型收窄
如何让TypeScript识别类中一个字段的取值会约束另一个字段的类型?
以下是需要实现的效果示例,核心诉求是判断commonName字段值时,自动收窄animal字段的对应类型:
class Cat { purr() {/*...*/} } class Dog { bark() {/*...*/} } interface TypeMap { cat: Cat; dog: Dog; } class Pet<C extends keyof TypeMap> { commonName: C; animal: TypeMap[C]; constructor(commonName : C, animal: TypeMap[C]) { this.commonName = commonName; this.animal = animal; } makeSound() { if(this.commonName === 'cat') { // 报错:this.animal 类型为 Cat | Dog,未按预期收窄为Cat return this.animal.purr(); } else if (this.commonName === 'dog') { // 报错:this.animal 类型为 Cat | Dog,未按预期收窄为Dog return this.animal.bark(); } } }
出现这个问题的核心原因是:TypeScript的控制流分析默认不会对泛型类的多个关联字段做自动收窄,TS无法保证字段判断和访问之间不存在值修改,因此不会默认建立两个字段的类型绑定关系。可以通过以下两种方案实现需求:
方案1:使用可辨识联合类型(类型安全度最高)
TS对可辨识联合的判别属性有原生收窄支持,不需要手动写类型断言,TS会自动校验类型逻辑的正确性。
实现代码:
class Cat { purr() {/*...*/} } class Dog { bark() {/*...*/} } interface TypeMap { cat: Cat; dog: Dog; } // 定义字段绑定的联合类型,以commonName作为判别键 type PetType = { commonName: 'cat'; animal: Cat; } | { commonName: 'dog'; animal: Dog; } class Pet { commonName: PetType['commonName']; animal: PetType['animal']; constructor(commonName: PetType['commonName']) { this.commonName = commonName; // 构造函数内初始化不会出现类型不兼容报错 if(commonName === 'cat') { this.animal = new Cat(); } else { this.animal = new Dog(); } } makeSound(this: PetType) { // 给this显式标注联合类型后,会自动触发判别收窄 if(this.commonName === 'cat') { // this.animal 自动收窄为Cat,无报错 return this.animal.purr(); } else if (this.commonName === 'dog') { // this.animal 自动收窄为Dog,无报错 return this.animal.bark(); } } }
方案2:保留泛型类结构,配合类型断言
如果业务场景必须保留泛型类的定义形式,可以在分支逻辑内手动做类型断言,指定当前分支下的this类型。
实现代码:
class Cat { purr() {/*...*/} } class Dog { bark() {/*...*/} } interface TypeMap { cat: Cat; dog: Dog; } class Pet<C extends keyof TypeMap> { commonName: C; animal: TypeMap[C]; constructor(commonName: C) { this.commonName = commonName; // 构造函数初始化时需要加断言,避免出现交叉类型不兼容报错 if(commonName === 'cat') { this.animal = new Cat() as TypeMap[C]; } else { this.animal = new Dog() as TypeMap[C]; } } makeSound() { if(this.commonName === 'cat') { // 断言当前实例为cat分支类型 return (this as Pet<'cat'>).animal.purr(); } else if (this.commonName === 'dog') { // 断言当前实例为dog分支类型 return (this as Pet<'dog'>).animal.bark(); } } }
注意:该方案需要手动保证分支判断逻辑和断言类型一致,TypeScript不会做额外的逻辑校验,如果分支判断存在遗漏或错误,会出现类型和实际运行逻辑不匹配的隐患。
内容的提问来源于stack exchange,提问作者WBT
相关产品推荐
相关产品推荐

