我正在尝试为通用表格组件编写一种类型,该组件接受列列表(每列都有名称和类型)以及需要符合列规定的形状的行列表。
我的代码仅在添加显式类型时才有效,但我更希望 typescript 能够为我推断这些类型:
enum ColumnType {
text = "text",
numeric = "numeric",
}
interface Props<T extends Record<string, ColumnType>> {
columns: Array<{ name: keyof T; columnType: ColumnType }>;
rows: Array<
{
[K in keyof T]: T[K] extends ColumnType.numeric
? { content: number }
: { content: string };
}
>;
}
function consume<T extends Record<string, ColumnType>>(arg: Props<T>): void {}
// missing fields, should not typecheck
consume({
columns: [
{ name: "a", columnType: ColumnType.numeric },
{ name: "b", columnType: ColumnType.text },
],
rows: [{}],
});
// type mismatch, should not typecheck
consume({
columns: [
{ name: "a", columnType: ColumnType.numeric },
{ name: "b", columnType: ColumnType.text },
],
rows: [{ a: { content: "asdf" }, b: { content: 42 } }],
});
// should typecheck, but doesn't
consume({
columns: [
{ name: "a", columnType: ColumnType.numeric },
{ name: "b", columnType: ColumnType.text },
],
rows: [{ a: { content: 42 }, b: { content: "asdf" } }],
});
// works with explicit type argument
consume<{ a: ColumnType.numeric; b: ColumnType.text }>({
columns: [
{ name: "a", columnType: ColumnType.numeric },
{ name: "b", columnType: ColumnType.text },
],
rows: [{ a: { content: 42 }, b: { content: "asdf" } }],
});
有没有办法用 typescript 来完成这个任务?
最佳答案
第一步是捕获有关列的更多信息。我们可以通过将整个数组设置为类型参数来做到这一点。为了确保我们捕获文字类型,我们将为 props 的名称和列类型添加一些类型参数,以帮助 TS 推断这些字段的文字类型,而不是推断其更广泛的类型。
function consume<T extends Array<{ name: K; columnType: CT }>, K extends PropertyKey, CT extends ColumnType>(arg: Props<T>): void {}
我们还希望捕获元组类型而不是数组类型,以便更轻松地处理数组的每个元素。为此,我们可以将 [] |
添加到类型约束中:
function consume<T extends [] | Array<{ name: K; columnType: CT }>, K extends PropertyKey, CT extends ColumnType>(arg: Props<T>): void {}
为了将数组的元素映射到对象中的字段,需要使用映射类型。
这里我们有两个选择。
第一个选项是在映射类型中使用 as
子句将元组的每个元素转换为字段。我们只需要过滤元组的索引(我们不需要数组成员)。为此,我们可以与 `${number}`
相交(元组索引实际上在类型系统 "0", "1"...
中表示为字符串)
type Row<T extends Array<{ name: PropertyKey; columnType: ColumnType }>> = {} & {
[K in keyof T & `${number}` as T[K]['name']]: T[K]['columnType'] extends ColumnType.numeric
? { content: number }
: { content: string };
}
与旧版本 TS 兼容的另一个选项是使用 number
进行索引,以获取元组所有元素的并集,映射由 name
产生的并集code> 属性,然后要获取每个字段的类型,请使用 Exatct
提取相应的联合组成部分:
type Row<T extends Record<number, { name: PropertyKey; columnType: ColumnType }>> = {} & {
[K in T[number]['name']]: Extract<T[number], { name: K } >['columnType'] extends ColumnType.numeric
? { content: number }
: { content: string };
}
把它们放在一起我们得到:
enum ColumnType {
text = "text",
numeric = "numeric",
}
type Row<T extends Array<{ name: PropertyKey; columnType: ColumnType }>> = {} & {
[K in keyof T & `${number}` as T[K]['name']]: T[K]['columnType'] extends ColumnType.numeric
? { content: number }
: { content: string };
}
type Props<T extends Array<{ name: PropertyKey; columnType: ColumnType }>> = {
columns: T;
rows: Array<Row<T>>;
}
function consume<T extends [] | Array<{ name: K; columnType: CT }>, K extends PropertyKey, CT extends ColumnType>(arg: Props<T>): void {}
关于typescript - 从数组类型泛型参数推断记录类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74099144/