Reputation: 158321
Not sure if this is possible, but it's related to keyof
which I'm having a hard time wrapping my mind around...
I currently have this interface:
interface Column<T> {
header: string;
field: keyof T;
format?: (value: any) => string;
}
First usage for it is to create something that renders a table, given a list of objects and columns.
const rows = [{name: 'Alice', age: 18}, {name: 'Bob', age: 12}];
type Row = typeof rows[0];
const columns: Column<Row>[] = [
{
header: 'Name',
field: 'name',
format: value => value.toUpperCase(),
},
{
header: 'Age',
field: 'age',
format: value => value.toLocaleString(),
},
];
const renderTable = <T>(rows: T[], columns: Columns<T>[]) { ... }
renderTable(rows, columns);
Is there a way to get rid of the any
type here in the format
function? To somehow "link" the keyof T
of field
with the value
of format
? I.e. for the first column value
should be string
, and for the second it should be number
?
(I know it would be very easy to just print these out manually, but there's also going to be added some auto-magic stuff to this table which will make use of field
and format
)
Upvotes: 0
Views: 629
Reputation: 5650
One approach is to rely on TypeScript's mapped types.
This will "map" through every key (Key in keyof
) in the generic passed (Row
).
Then for each Row
key (eg: name
, age
, admin
) we'll assign that an object of the shape you outlined with the properties header
, field
and format
.
However, since we now know we're working with a specific key (eg: age
) we can use Key
to reference age
in this mapped typed and Row[Key]
to reference the type of age
(number
).
Finally, you don't want keys with nested objects, you want only the objects so we can effectively "pluck" those values with [keyof Row]
, looking up all the values for "age" | "name" | "admin"
and what is left is the union type of all the possible columns with the field
and format
correctly typed.
type Columns<Row> = {
[Key in keyof Row]: {
header: string;
field: Key,
format?: (value: Row[Key]) => string;
}
}[keyof Row]
const rows = [{name: 'Alice', age: 18, admin: true }, {name: 'Bob', age: 12, admin: false}];
type Row = typeof rows[0];
const columns: Columns<Row>[] = [
{
header: 'Name',
field: 'name',
format: value => value.toUpperCase(), // value: string
},
{
header: 'Age',
field: 'age',
format: value => value.toLocaleString(), // value: number
},
{
header: 'Admin',
field: 'admin',
format: value => value.toString(), // value: boolean
},
];
Upvotes: 2