Reputation: 655
I'm stumped, hopefully this simplified example explains it well enough:
I have a class instance holding data, and another composite class instance to render the data. The data class gives callbacks to UI class in order to get and set data properties. I'm stumped on the "set" too but I'll stick to "getVal" for now.
class Person {
private _data: DataObj
constructor() {
this._ui = new PersonUI({
getVal: (key) => this.getVal(key)
})
}
getVal<T extends keyof DataObj>( key: T ) {
return this._data[key]
}
}
class PersonUI {
constructor(arg: UIArg) {}
}
Typescript can infer the type of the _data value, but I don't know how to write an interface for UIArg that will keep that inferred type, all I could come up with was "any" ex:
interface UIArg {
getVal: <T extends keyof DataObj>(key: T) => any
}
Is there a way to write the interface to keep the inferred type?
Bonus Q: is there a way to write the Person.p.setVal method to use the inferred type too? Ex:
setVal<T extends keyof DataObj>(key: T, val: any) {
this._data[key] = val
}
Upvotes: 1
Views: 67
Reputation: 327624
You're looking for indexed access types. If you have a type T
and a key type K extends keyof T
, then the property at that key will have a value of type T[K]
. In your case, DataObj
is taking the role of T
, and I will use K
to represent the key type (which is more idiomatic than T
)... so the value type in question is DataObj[K]
:
interface UIArg {
getVal: <K extends keyof DataObj>(key: K) => DataObj[K],
setVal: <K extends keyof DataObj>(key: K, val: DataObj[K]) => void;
}
class Person {
private _data: DataObj
private _ui: PersonUI;
constructor(data: DataObj) {
this._data = data;
this._ui = new PersonUI({
getVal: key => this.getVal(key),
setVal: (key, val) => this.setVal(key, val)
})
}
getVal<K extends keyof DataObj>(key: K): DataObj[K] {
return this._data[key]
}
setVal<K extends keyof DataObj>(key: K, val: DataObj[K]) {
this._data[key] = val
}
}
This compiles with no errors.
Upvotes: 1