It's possible to declare a interface like this;
interface TableColumn<T> {
fieldName: key of T
render: (value: **value type of the fieldName** ) => void
}
I want to make a type related to keyof T
.
this is what I want to do:
type Person {
name: string;
age: number;
}
interface TableColumn<T> {
fieldName: keyof T;
render: (value: /* type of keyof T */) => unknown;
}
//eaxample:
const NameColumn: TableColumn<Person> = {
fieldName: 'name', // keyof T is Person[name]
render: (value: ??) // ==> value must be a string type, becasuse Person.name is a string type
}
const AgeColumn: TableColumn<Person> = {
fieldName: 'age', // keyof T is Person[age]
render: (value: ??) // ==> value must be number type, becasuse Person.age is a number type
}
If we just use the following code:
type TableColumn<T, K extends keyof T = keyof T> = {
fieldName: K;
render: (value: T[K]) => unknown;
};
The result won't be the expected one unfortunately, because the value
in the render
function will have the type of a union of all possible types in T
. This can be fixed by using the conditional type distribution. When a distribution is applied to a union the code after the condition is applied to each member of the union separately:
type FirstToArray<T> = T[]
type First = FirstToArray<string | number> // (string | number)[]
type SecondToArray<T> = T extends any ? T[] : never
type Second = SecondToArray<string | number> // string[] | number[]
Let's apply this logic to our type:
type TableColumn<T, K extends keyof T = keyof T> = K extends K ? {
fieldName: K;
render: (value: T[K]) => unknown;
} : never
Testing:
type ColumnsMap = {
field1: string;
field2: number;
};
const a: TableColumn<ColumnsMap> = {
fieldName: 'field1',
render: (value) => '' // value: string
}