
通过泛型与条件类型组合,可让 TypeScript 在编译期静态推断出仅在特定上下文中有效的属性,对非法访问(如 myClass.dog 在 'birds' 上下文中)触发类型错误,从而实现真正安全的条件字段访问。
通过泛型与条件类型组合,可让 typescript 在编译期静态推断出仅在特定上下文中有效的属性,对非法访问(如 `myclass.dog` 在 `'birds'` 上下文中)触发类型错误,从而实现真正安全的条件字段访问。
原方案中直接将所有字段声明为 string | never 并在运行时赋值,无法达成预期的类型检查效果:TypeScript 会将 never 视为“不可达类型”,但只要字段被声明(哪怕未赋值),其类型仍被宽泛地视为 string | never,而 string | never 等价于 string —— 因此 myClass.dog 不会报错,违背了设计初衷。
✅ 正确解法是使用泛型 + 条件类型(Conditional Types)+ 类型守卫,让实例类型随 context 参数精确收敛。核心思路是:定义一个映射类型,将每个 Context 值映射到其对应允许的字段集合,并让类泛型化该上下文:
type Context = 'birds' | 'mammals';
// 定义各上下文允许的字段及其类型
type ContextFields<C extends Context> = C extends 'birds'
? { readonly chicken: string; readonly duck: string }
: C extends 'mammals'
? { readonly dog: string; readonly cat: string }
: never;
// 泛型类:类型参数 C 决定实例的字段签名
class MyClass<C extends Context> {
// 使用类型断言确保字段只存在于对应上下文中
private _fields: ContextFields<C>;
constructor(context: C) {
if (context === 'birds') {
this._fields = { chicken: 'chicken', duck: 'duck' } as ContextFields<C>;
} else {
this._fields = { dog: 'dog', cat: 'cat' } as ContextFields<C>;
}
}
// 代理访问:TS 会根据 C 精确推导出可用属性
get chicken(): C extends 'birds' ? string : never {
return (this._fields as any).chicken;
}
get duck(): C extends 'birds' ? string : never {
return (this._fields as any).duck;
}
get dog(): C extends 'mammals' ? string : never {
return (this._fields as any).dog;
}
get cat(): C extends 'mammals' ? string : never {
return (this._fields as any).cat;
}
}
// ✅ 类型安全调用
const birds = new MyClass<'birds'>('birds');
console.log(birds.chicken); // ✅ OK
// console.log(birds.dog); // ❌ TS2339: Property 'dog' does not exist on type 'MyClass<"birds">'
const mammals = new MyClass<'mammals'>('mammals');
console.log(mammals.dog); // ✅ OK
// console.log(mammals.duck); // ❌ TS2339: Property 'duck' does not exist on type 'MyClass<"mammals">'? 进阶优化(推荐):使用 as const + 分布式条件类型 + 构造函数重载,实现自动类型推导,无需手动指定泛型参数:
class MyClass<C extends Context = Context> {
private constructor(
private readonly context: C,
private readonly fields: ContextFields<C>
) {}
static create<C extends Context>(context: C): MyClass<C> {
if (context === 'birds') {
return new MyClass(context, { chicken: 'chicken', duck: 'duck' }) as MyClass<C>;
} else {
return new MyClass(context, { dog: 'dog', cat: 'cat' }) as MyClass<C>;
}
}
get chicken(): C extends 'birds' ? string : never {
return (this.fields as any).chicken;
}
// ... 其他 getter 同理
}
// 自动推导类型 ✅
const myClass = MyClass.create('birds'); // 类型为 MyClass<"birds">
// myClass.dog; // ❌ 编译错误⚠️ 注意事项:
- 避免在字段声明中直接使用
string | never,它无法触发有效错误;必须依赖泛型约束和条件类型驱动类型收敛; - 运行时逻辑(
if分支)无法影响类型系统,所有类型安全必须在编译期通过泛型、extends和条件类型完成; - 若需支持更多上下文或动态字段,可将
ContextFields改为映射接口(Record<context record unknown>></context>),并配合keyof和in操作符增强可维护性。
这种模式广泛应用于配置驱动型 SDK、多环境客户端(如 NodeClient / BrowserClient)、以及领域模型中状态受限的实体设计,是 TypeScript 高级类型工程的典型实践。


















