TypeScript如何创建类内部类型?规避泛型方案的覆盖问题
TypeScript类内部定义关联泛型类型的解决方案
问题说明
TypeScript不允许在类的顶层直接定义依赖类泛型的内部类型,比如下面的写法会报错:
class Example<Data extends Record<string, any>> { type Key = keyof Data; // 这里会报错,类内部不能这么定义类型 getKeys(): Key[] { /* ... */ } }
但在函数作用域里,这种类型定义是完全合法的:
function getKeys<Data extends Record<string, any>>(data: Data) { type Key = keyof Data; // 没问题 const keys: Key[] = Object.keys(data) as Key[]; return keys; }
现有泛型方案的问题
之前用双泛型参数的方案有明显缺陷:
class Example<Data extends Record<string, any>, Key = keyof Data> { getKeys(): Key[] { return []; } } const example = new Example<{aKey: 'value'}>(); const example2 = new Example<{aKey: 'value'}, "aKey">(); // 外部可以随便覆盖Key类型
- 泛型
Key能被外部手动覆盖,破坏了它本该和Data绑定的关系 - 因为
Key可篡改,TypeScript没法安全推断返回类型,经常需要额外类型转换,容易出问题
实用的变通方法
1. 方法内定义类型(推荐)
把依赖泛型的类型定义放到类方法内部,和函数里的写法一致,既保证类型和Data绑定,又不会被外部篡改:
class Example<Data extends Record<string, any>> { private data: Data; constructor(data: Data) { this.data = data; } getKeys(): (keyof Data)[] { type Key = keyof Data; // 方法内部可以正常定义 const keys = Object.keys(this.data) as Key[]; return keys; } }
2. 静态泛型工具方法封装
如果多个方法都要用到这个类型,可以用类的静态方法封装类型逻辑,确保类型和Data强绑定:
class Example<Data extends Record<string, any>> { private data: Data; constructor(data: Data) { this.data = data; } getKeys() { return Example.extractKeys(this.data); } // 静态方法单独处理类型逻辑 static extractKeys<Data extends Record<string, any>>(data: Data) { type Key = keyof Data; return Object.keys(data) as Key[]; } }
3. 外部类型别名绑定
如果需要在类外部也复用这个关联类型,可以定义一个和类泛型绑定的外部类型别名:
// 外部定义和Data绑定的类型 type ExampleKey<Data> = keyof Data; class Example<Data extends Record<string, any>> { private data: Data; constructor(data: Data) { this.data = data; } getKeys(): ExampleKey<Data>[] { return Object.keys(this.data) as ExampleKey<Data>[]; } }
这种方式下,ExampleKey永远和Data绑定,没法单独篡改。
4. 直接用keyof Data作为返回类型(最简洁)
如果只是想避免泛型被覆盖,完全不用额外泛型参数,直接把返回类型设为(keyof Data)[]:
class Example<Data extends Record<string, any>> { private data: Data; constructor(data: Data) { this.data = data; } getKeys(): (keyof Data)[] { return Object.keys(this.data) as (keyof Data)[]; } }
这是最省事的方案,直接让返回类型和Data强绑定,从根源上杜绝了泛型被覆盖的问题。
内容的提问来源于stack exchange,提问作者Flavien Volken
相关产品推荐
相关产品推荐

