You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于类成员关联的TypeScript类型推断与约束实现

TypeScript 关联类型类的优化实现

需求概述

  • 类Foo的fieldOne类型为number | string,fieldTwo类型与fieldOne强关联:
    • 当fieldOne是number时,fieldTwo必须是Bar[]
    • 当fieldOne是string时,fieldTwo必须是Baz[]
  • 构造函数传入两个字段,编译器需约束只能使用合法的类型组合实例化
  • 类内大部分逻辑基于Bar和Baz的联合类型,需支持在特定逻辑中收窄fieldTwo的类型到Bar[]或Baz[]

基础接口定义

interface Bar {
  foo: string;
  bar: number;
}

interface Baz {
  foo: string;
  baz: number;
}

已尝试方案及问题

1. 继承方案

通过抽象基类+子类实现类型约束,但需要创建多个子类,结构繁琐:

abstract class Foo<T extends number | string, U extends Bar[] | Baz[]> {
    private fieldOne: T;
    protected fieldTwo: U;

    constructor(paramOne: T, paramTwo: U) {
        this.fieldOne = paramOne;
        this.fieldTwo = paramTwo;
    }
    
    generalFunction() {
        console.log(this.fieldTwo.map(x => x.foo).join());
        this.specificFunction();
    }
    
    protected abstract specificFunction(): void;
}

class FooBar extends Foo<number, Bar[]> {
    specificFunction() {
        console.log(this.fieldTwo.map(x => x.bar).join());
    }
}

class FooBaz extends Foo<string, Baz[]> {
    specificFunction() {
        console.log(this.fieldTwo.map(x => x.baz).join());
    }
}

2. 条件类型方案

尝试用泛型条件类型关联字段,但未实现类型收窄:

type FooContents<T extends number | string> = T extends number ? Bar : Baz;

class Foo<T extends number | string> {
  private fieldOne: T;
  private fieldTwo: FooContents<T>;

  constructor(param1: T, param2: FooContents<T>) {
    this.fieldOne = param1;
    this.fieldTwo = param2;
  }

  generalFunction() {
    console.log(this.fieldTwo.foo);
  }

  specificFunction() {
    // 无法自动收窄fieldTwo类型
  }
}

3. 联合参数对象方案

将字段合并为联合类型对象,但需要额外类型守卫函数,代码冗余(已修正原代码中的判断错误):

type FooParams = {one: number, two: Bar[]} | {one: string, two: Baz[]};
class Foo {
    params: FooParams;

    constructor(params: FooParams) {
      this.params = params;
    }
    
    generalFunction() {
        console.log(this.params.two.map(x => x.foo).join());
    }

    specificFunction() {
      if (isNumberyBary(this.params)) this.processBars(this.params.two);
      if (isStringyBazy(this.params)) this.processBazs(this.params.two);
    }

    processBars(bars: Bar[]) {
        console.log(bars.map(x => x.bar).join());
    }

    processBazs(bazs: Baz[]) {
        console.log(bazs.map(x => x.baz).join());
    }
}

function isNumberyBary(x: FooParams): x is {one: number, two: Bar[]} {
  return typeof x.one === 'number';
}

function isStringyBazy(x: FooParams): x is {one: string, two: Baz[]} {
  return typeof x.one === 'string';
}

优化实现方案

利用泛型条件类型+类内类型守卫,实现简洁且类型安全的方案:

版本1:基础实现(依赖typeof判断)

type FieldTwoType<T extends number | string> = T extends number ? Bar[] : Baz[];

class Foo<T extends number | string> {
  private fieldOne: T;
  private fieldTwo: FieldTwoType<T>;

  constructor(paramOne: T, paramTwo: FieldTwoType<T>) {
    this.fieldOne = paramOne;
    this.fieldTwo = paramTwo;
  }

  // 通用逻辑:基于Bar/Baz的公共属性操作
  generalFunction() {
    console.log(this.fieldTwo.map(item => item.foo).join(','));
    this.specificFunction();
  }

  // 特定逻辑:通过typeof自动收窄类型
  specificFunction() {
    if (typeof this.fieldOne === 'number') {
      // 编译器自动推断fieldTwo为Bar[]
      console.log(this.fieldTwo.map(item => item.bar).join(','));
    } else {
      // 编译器自动推断fieldTwo为Baz[]
      console.log(this.fieldTwo.map(item => item.baz).join(','));
    }
  }
}

// 合法实例化
const fooBar = new Foo(123, [{ foo: "bar1", bar: 1 }, { foo: "bar2", bar: 2 }]);
const fooBaz = new Foo("abc", [{ foo: "baz1", baz: 3 }, { foo: "baz2", baz: 4 }]);

// 非法实例化:编译器报错(不符合类型约束)
// const invalidFoo = new Foo(123, [{ foo: "baz", baz: 5 }]);

版本2:自定义类型守卫(更清晰的类型收窄)

如果需要更明确的类型判断,可以添加类内类型守卫方法,完全避免类型断言:

type FieldTwoType<T extends number | string> = T extends number ? Bar[] : Baz[];

class Foo<T extends number | string> {
  private fieldOne: T;
  private fieldTwo: FieldTwoType<T>;

  constructor(paramOne: T, paramTwo: FieldTwoType<T>) {
    this.fieldOne = paramOne;
    this.fieldTwo = paramTwo;
  }

  generalFunction() {
    console.log(this.fieldTwo.map(item => item.foo).join(','));
    this.specificFunction();
  }

  // 自定义类型守卫:判断当前实例是否为number+Bar[]组合
  isBarInstance(): this is Foo<number> {
    return typeof this.fieldOne === 'number';
  }

  specificFunction() {
    if (this.isBarInstance()) {
      // 无需断言,fieldTwo自动为Bar[]
      console.log(this.fieldTwo.map(item => item.bar).join(','));
    } else {
      // fieldTwo自动为Baz[]
      console.log(this.fieldTwo.map(item => item.baz).join(','));
    }
  }
}

方案优势

  • 严格类型约束:编译器自动校验构造函数参数的类型匹配,杜绝非法组合
  • 实现简洁:单个类即可完成需求,无需子类或额外参数包装
  • 自然类型收窄:通过typeof或自定义类型守卫,编译器自动推导fieldTwo的具体类型,无需手动断言

内容的提问来源于stack exchange,提问作者eoinmullan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 20:22:05