如何为存在TS不兼容方法重写的第三方JS库编写模块声明文件?
如何在TypeScript声明文件中如实表示不符合标准的方法重写
首先,你遇到的是TypeScript类型系统和第三方JS库实际行为的冲突:TypeScript要求子类重写的方法必须满足参数逆变、返回协变的兼容性规则,但你的第三方库中子类User的can方法和父类Entity的can方法签名完全不匹配,这在原生TS中会触发重写错误。
我们不需要歪曲Entity和User的继承关系,有几种方案可以如实声明这种情况同时避免tsc报错:
方案1:使用// @ts-ignore跳过单方法的重写检查
这是最直接的方式,因为我们是在描述第三方库的实际行为,不需要强制符合TS的类型规则。在子类的can方法声明前添加忽略注释即可:
declare class Entity { /** * 测试指定User是否拥有对该Entity执行某操作的权限 * @param {User} user 请求操作的User * @param {string} action 尝试执行的操作 * @return {boolean} 用户是否拥有权限? */ can(user: User, action: string): boolean; } declare class User extends Entity { /** * 测试User是否能够执行某权限操作。游戏管理员用户始终被允许执行所有操作,不受权限限制。 * * @param {string} permission 要测试的操作 * @return {boolean} 用户是否能够执行该操作? */ // @ts-ignore 跳过TS的重写兼容性检查,如实反映第三方库的方法签名 can(permission: string): boolean; }
这个方案的优势是完全保留了类的继承关系和方法的真实签名,只是告诉TS忽略这个特定的兼容性错误。
方案2:使用接口合并扩展子类类型
如果不想使用注释,我们可以通过接口合并的方式,为User类添加额外的方法签名,同时保留继承关系:
declare class Entity { can(user: User, action: string): boolean; } declare class User extends Entity {} // 接口合并,为User添加真实的can方法签名 interface User { can(permission: string): boolean; }
这种方式下,TS会认为User同时拥有父类的can方法和我们声明的新can方法,相当于方法重载。虽然这和JS库的实际行为(子类重写了父类方法,而非重载)有一点差异,但可以避免报错,同时用户调用User的can方法时会得到正确的类型提示。
方案3:将父类方法声明为宽泛的兼容类型
如果这类不兼容重写的场景很多,你可以考虑将父类的can方法声明为更宽泛的类型,让子类的签名自然兼容:
declare class Entity { can(...args: any[]): boolean; } declare class User extends Entity { can(permission: string): boolean; }
不过这个方案的缺点是会丢失父类can方法的具体类型提示,所以只适合你不需要严格约束父类方法调用的场景。
总结来说,方案1是最推荐的,因为它完全如实反映了第三方库的实际情况,没有歪曲继承关系或方法签名,只是通过注释跳过了TS的强制兼容性检查。
内容的提问来源于stack exchange,提问作者Richard Simões
相关产品推荐
相关产品推荐

