Sean
Sean

Reputation: 655

Typescript Generics keyof parameter signature

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

Answers (1)

jcalz
jcalz

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.

Playground link to code

Upvotes: 1

Related Questions