Typescript: Object with keys' types conditioned by other keys of the same object
好的,所以我有这个功能,应该从一个对象接收一个键和一个值。不同的键具有与之关联的不同的值类型。
我不想有一个通用的功能,例如:
1 2 3 | function updateField(key: string, value: any) { } |
例如,我在这里有这个对象:
1 2 3 4 5 6 7 | interface ISessionSpecific { students?: number[]; subject?: number; address?: string; duration?: string[]; date?: Date; } |
我想创建一个更新此对象中字段的函数,但我希望它正确键入...
所以当我打电话时:
1 2 | const value = something; updateField('address', value); |
如果value不是字符串类型,则会抛出错误;
我尝试过的事情:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // First Approach: type ForField<T extends keyof ISessionSpecific> = { field: T; value: Required<ISessionSpecific>[T]; }; type Approach1 = | ForField<'address'> | ForField<'students'> | ForField<'date'> | ForField<'duration'> | ForField<'subject'>; // Second Approach type Approach2<T extends ISessionSpecific = ISessionSpecific> = { field: keyof T; value: T[keyof T]; }; // Testing const x: [Approach1, Approach2] = [ { field: 'address', value: 0 }, // Error { field: 'address', value: 0 }, // No Error ]; |
我的第一种方法解决了我的问题,但我认为这太冗长了。因为我创建的此接口只是一个示例,所以实际的接口可能更大。
所以我想知道是否有任何更优雅的方法可以做到这一点
您可以利用分布条件类型来获取类似于自动生成的
1 2 3 | type MapToFieldValue<T, K = keyof T> = K extends keyof T ? { field: K, value: T[K] } : never; const foo: MapToFieldValue<ISessionSpecific> = { field: 'address', value: 0 } // Expect error; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | type ManualMap = { field:"students"; value: number[] | undefined; } | { field:"subject"; value: number | undefined; } | { field:"address"; value: string | undefined; } | { field:"duration"; value: string[] | undefined; } | { field: 'date'; value: Date | undefined; } |
使用映射类型的另一种方法会产生相同的结果(感谢@Titian):
1 | type MapToFieldValue< T > = { [K in keyof T]: { field: K, value: T[K] } }[keyof T] |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | interface ISessionSpecific { students?: number[]; subject?: number; address?: string; duration?: string[]; date?: Date; } function updateField<T, P extends keyof T>(obj: T, prop: P, value: T[P]) { obj[prop] = value; } var myObj: ISessionSpecific = {}; updateField(myObj,"date", new Date()); // OK! updateField(myObj,"nonExistingProp","some Value"); // Error"nonExistingProp" is not a valid prop. let subject1 = 10; // inferred type : number let subject2 ="10"; // inferred type : string updateField(myObj,"subject", subject1); // OK! updateField(myObj,"subject", subject2); // Error Argument of type 'string' is not assignable to parameter of type 'number | undefined'. updateField(myObj,"subject", undefined); // OK because ISessionSpecific has subject as optional // if you want the 3rd paramater to be not null or undefined you need to do this: function updateFieldNotUndefined<T, P extends keyof T>(obj: T, prop: P, value: Exclude<T[P], null | undefined>) { obj[prop] = value; } updateFieldNotUndefined(myObj,"subject", undefined); // ERROR! // If you want to pass an object of Key-Value pairs: function updateFieldKeyValuePair<T, P extends keyof T>( obj: T, kvp: { prop: P, value: Exclude<T[P], null | undefined> } ) { obj[kvp.prop] = kvp.value; } // if you want to put it in a class: class SessionSpecific implements ISessionSpecific { students?: number[]; subject?: number; address?: string; duration?: string[]; date?: Date; public updateField<P extends keyof this>(prop: P, value: this[P]) { this[prop] = value; } } |
游乐场