What I want to do:
type Apple = {
seeds: number,
size: number
};
const apple = {
seeds: 5,
size: 2
};
double(apple, 'seeds');
double(apple, 'size');
console.log(apple); // => { seeds: 10, size: 4 }
This is trivial in JS:
function double(object, property) {
object[property] *= 2;
}
In TS it’s an absolute nightmare. This obviously doesn’t work:
function double<T>(object: T, property: keyof T) {
object[property] *= 2;
}
The left-hand side of an arithmetic operation must be of type ‘any’, ‘number’, ‘bigint’ or an enum type
I had high hopes of this working, but… no luck:
function double<T extends { [k in K]: number }, K extends keyof T>(object: T, property: K) {
object[property] *= 2;
}
Type ‘number’ is not assignable to type ‘T[K]’.
‘number’ is assignable to the constraint of type ‘T[K]’, but ‘T[K]’ could be instantiated with a different subtype of constraint ‘number’.
I think it’s odd that TS should enforce this when it won’t do so for non-generics, but so be it. Any better ideas?
>Solution :
There are a couple of options here:
First. No need to define a generic for an object, only define K and make object parameter be of type Record<K, number>:
function double<K extends string>(object: Record<K, number>, property: K) {
object[property] *= 2;
}
double(apple, 'seeds');
double(apple, 'size');
double(apple, 'invalid'); // error
The downside of this approach is that there are no autosuggestions for the property
Second. Define two generics T and K extends keyof T, however, we won’t type object as T but rather just a Record<K, number>:
function double<T, K extends keyof T>(object: Record<K, number>, property: K) {
object[property] *= 2;
}
double(apple, 'seeds'); // autosuggestions working
double(apple, 'incorrect'); // error
The advantage is that autosuggestions are now working. The compiler is able to infer the type for T the because we have typed object as Record<K, number>, and since K extends keyof T, object is T. The downside of this method is that object is required to have only number fields, otherwise the compiler will raise an error, since K will be a union of all keys, meaning that all fields of T are numbers.