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

如何在TypeScript中用泛型推导/收窄示例中的值类型?

问题

我希望在以下代码片段中根据属性名称推导/收窄对应的值类型,但当前方案无效。目标是实现类似可区分联合在if语句中提供的类型安全级别:

type UserProperty = Pick<User, 'firstName'|'lastName'|'age'>;

// 这个类型无法根据propertyName正确推导propertyValue的类型
type OnChangedFunction<T extends UserProperty = UserProperty> = (args: { propertyName: keyof T, propertyValue: T[keyof T]}) => void;

class User {

  private readonly _onChangedCallback: OnChangedFunction;
  constructor(onChangedCallback: OnChangedFunction) {
    this._onChangedCallback = onChangedCallback;
  }

  private _firstName: string = "";
  public get firstName(): string { return this._firstName; }
  public set firstName(v: string) {
    if (v !== this._firstName) {
      this._firstName = v;
      this._onChangedCallback({ propertyName: "firstName", propertyValue: v });
      
    }
  }

  private _lastName: string = "";
  public get lastName(): string { return this._lastName; }
  public set lastName(v: string) {
    if (v !== this._lastName) {
      this._lastName = v;
      this._onChangedCallback({ propertyName: "lastName", propertyValue: v });
    }
  }

  private _age: number = 0;
  public get age(): number { return this._age; }
  public set age(v: number) {
    if (this._age !== v) {
      this._age = v;
      this._onChangedCallback({ propertyName: "age", propertyValue: v });
    }
  }
}

const f: OnChangedFunction = (args) => {
  if (args.propertyName === "firstName" || args.propertyName === "lastName") {
    console.log(`我希望写代码时知道'${args.propertyName}'的类型是string!(${args.propertyValue})`);
  }
  else {
    console.log(`我希望写代码时知道'${args.propertyName}'的类型是number!(${args.propertyValue})`);
  }
}

const u = new User(f);
u.age = 56;
u.firstName = "John";
u.lastName = "Doe";
解决方案

要实现属性名和属性值的类型关联,需要将回调参数定义为可区分联合类型,而不是让属性名和属性值类型各自独立。修改后的完整代码如下:

class User {
  private readonly _onChangedCallback: OnChangedFunction;
  constructor(onChangedCallback: OnChangedFunction) {
    this._onChangedCallback = onChangedCallback;
  }

  private _firstName: string = "";
  public get firstName(): string { return this._firstName; }
  public set firstName(v: string) {
    if (v !== this._firstName) {
      this._firstName = v;
      this._onChangedCallback({ propertyName: "firstName", propertyValue: v });
    }
  }

  private _lastName: string = "";
  public get lastName(): string { return this._lastName; }
  public set lastName(v: string) {
    if (v !== this._lastName) {
      this._lastName = v;
      this._onChangedCallback({ propertyName: "lastName", propertyValue: v });
    }
  }

  private _age: number = 0;
  public get age(): number { return this._age; }
  public set age(v: number) {
    if (this._age !== v) {
      this._age = v;
      this._onChangedCallback({ propertyName: "age", propertyValue: v });
    }
  }
}

// 生成每个属性名对应其类型的联合类型
type UserPropertyChange = {
  [K in keyof Pick<User, 'firstName'|'lastName'|'age'>]: {
    propertyName: K;
    propertyValue: User[K];
  }
}[keyof Pick<User, 'firstName'|'lastName'|'age'>];

type OnChangedFunction = (args: UserPropertyChange) => void;

const f: OnChangedFunction = (args) => {
  if (args.propertyName === "firstName" || args.propertyName === "lastName") {
    // TypeScript自动推断args.propertyValue为string类型
    console.log(`现在明确'${args.propertyName}'的类型是string!(${args.propertyValue.toUpperCase()})`);
  } else {
    // TypeScript自动推断args.propertyValue为number类型
    console.log(`现在明确'${args.propertyName}'的类型是number!(${args.propertyValue.toFixed(0)})`);
  }
}

const u = new User(f);
u.age = 56;
u.firstName = "John";
u.lastName = "Doe";

关键改动说明

  1. 构建可区分联合类型UserPropertyChange:

    • 通过映射类型为每个目标属性生成独立的对象类型,每个类型中propertyName是具体的字面量类型(如"firstName"),propertyValue对应属性的精确类型(如string)。
    • 最后通过索引访问将映射类型转换为联合类型,确保每个联合成员的属性名和属性值类型严格绑定。
  2. 调整OnChangedFunction定义:

    • 移除泛型,直接接收UserPropertyChange类型参数。TypeScript会自动识别这是可区分联合,通过propertyName的取值自动收窄propertyValue的类型。

修改后,在if分支中编写代码时,TypeScript会根据propertyName自动推导propertyValue的精确类型,完全实现目标类型安全级别。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 12:52:01