TypeScript仅实现抽象方法的子类可互换问题及解决方法
问题原因
TypeScript采用结构类型系统,类型判断的依据是值的实际结构,而非类的名称(名义)。UserId和OrganizationId的结构完全一致:都继承自抽象类Id,且仅实现了返回string类型的getPrefix方法,没有任何独特的属性或方法差异。因此TypeScript会判定这两个类的实例属于兼容类型,允许互相赋值,自然不会在调用getUser(orgId)时抛出预期的错误。
解决方法
通过给类添加名义类型标识,让TypeScript能够区分不同类的实例,常用的几种实现方式如下:
1. 添加私有属性
给每个子类添加一个唯一的私有属性,私有属性不会被结构类型系统忽略(外部无法访问),以此制造结构差异:
abstract class Id { public abstract getPrefix(): string; } class UserId extends Id { private readonly _userIdBrand: undefined; // 专属私有标识 public getPrefix(): string { return 'user'; } } class OrganizationId extends Id { private readonly _organizationIdBrand: undefined; // 专属私有标识 public getPrefix(): string { return 'organization'; } } const getUser = (id: UserId) => {}; const userId = new UserId(); const orgId = new OrganizationId(); getUser(userId); getUser(orgId); // 现在会抛出预期的类型错误
2. 添加只读公共标识属性
给每个子类添加一个值唯一的readonly公共属性,通过不同的属性值让TypeScript识别结构差异:
abstract class Id { public abstract getPrefix(): string; public abstract readonly type: string; } class UserId extends Id { public readonly type = 'user'; public getPrefix(): string { return this.type; } } class OrganizationId extends Id { public readonly type = 'organization'; public getPrefix(): string { return this.type; } } const getUser = (id: UserId) => {}; const userId = new UserId(); const orgId = new OrganizationId(); getUser(userId); getUser(orgId); // 类型错误:OrganizationId 无法赋值给 UserId 类型的参数
3. 使用品牌类型(Brand Types)
通过类型别名包装类实例,给不同的ID类型添加专属品牌标记,无需修改类的原始实现:
abstract class Id { public abstract getPrefix(): string; } // 给不同ID类型添加品牌标记 type UserId = Id & { readonly _brand: 'userId' }; type OrganizationId = Id & { readonly _brand: 'organizationId' }; // 封装实例创建逻辑,通过类型断言添加品牌 class IdImpl extends Id { private prefix: string; constructor(prefix: string) { super(); this.prefix = prefix; } public getPrefix(): string { return this.prefix; } } const createUserId = (): UserId => new IdImpl('user') as UserId; const createOrganizationId = (): OrganizationId => new IdImpl('organization') as OrganizationId; const getUser = (id: UserId) => {}; const userId = createUserId(); const orgId = createOrganizationId(); getUser(userId); getUser(orgId); // 类型错误
内容的提问来源于stack exchange,提问作者Kousha
相关产品推荐
相关产品推荐

