I am trying to define types so that the type of Row
depends on the values provided in Column
.
For example
type Column = { key: string; title: string };
type Row = {
// ??
};
const table = (columns: Column[], rows: Row[]) => {};
table(
[
{ key: "firstName", title: "First Name" },
{ key: "address", title: "Address" },
],
[{ firstName: "mKman", address: "1 First St" }] // how to enforce this ?
);
CodePudding user response:
In order to do that, you need to infer each key
property of each column:
type TableColumn = {
key: string;
title: string
};
const table = <
Key extends string,
Column extends TableColumn & { key: Key },
Columns extends Column[],
Row extends Record<Columns[number]['key'], string>,
Rows extends Row[]
>(columns: [...Columns], rows: [...Rows]) => {};
table(
[
{ key: "firstName", title: "First Name" },
{ key: "address", title: "Address" },
],
[{ firstName: "mKman", address: "1 First St" }] // ok
);
table(
[
{ key: "firstName", title: "First Name" },
{ key: "address", title: "Address" },
],
[{ address: "1 First St" }] // expected error, no firstName
);
table(
[
{ key: "firstName", title: "First Name" },
{ key: "address", title: "Address" },
],
[{ firstName:'John', addres: "1 First St" }] // expected error, addres is written with type, should be double ss
);
You can find more information about function argument inference in my article
CodePudding user response:
In order capture a type for use somewhere else, you need generics.
const table = <T extends readonly Column[]>(
columns: T,
rows: Record<T[number]['key'], string>[]
) => {};
Here the type of the array of columns
objects is captured as T
.
Then rows
uses that type. Indexing an array type by [number]
gets the type of the array members. And then indexing that by ['key']
looks up the property named key on those objects.
In the case of your example:
T[number]['key']
Would be of type:
"firstName" | "address"
You can now make a Record
type with that union of string literals as keys, that have string
as the value type.
Record<T[number]['key'], string>
You also need to make sure you pass in your schema as const
in order to capture the string literal types, instead of just string
.
Putting all that together:
type Column = { key: string; title: string };
type Row<T extends readonly { key: string }[]> =
Record<T[number]['key'], string>;
const table = <T extends readonly Column[]>(
columns: T,
rows: Row<T>[]
) => {};
table(
[
{ key: "firstName", title: "First Name" },
{ key: "address", title: "Address" },
] as const,
[
{ firstName: "mKman", address: "1 First St" }, // fine
{ lastName: "mKman", address: "2 Second St" }, // error
]
);