如何在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";
关键改动说明
构建可区分联合类型
UserPropertyChange:- 通过映射类型为每个目标属性生成独立的对象类型,每个类型中
propertyName是具体的字面量类型(如"firstName"),propertyValue对应属性的精确类型(如string)。 - 最后通过索引访问将映射类型转换为联合类型,确保每个联合成员的属性名和属性值类型严格绑定。
- 通过映射类型为每个目标属性生成独立的对象类型,每个类型中
调整
OnChangedFunction定义:- 移除泛型,直接接收
UserPropertyChange类型参数。TypeScript会自动识别这是可区分联合,通过propertyName的取值自动收窄propertyValue的类型。
- 移除泛型,直接接收
修改后,在if分支中编写代码时,TypeScript会根据propertyName自动推导propertyValue的精确类型,完全实现目标类型安全级别。
内容的提问来源于stack exchange,提问作者AlexG
相关产品推荐
相关产品推荐

