我想编写一个非常聪明的DataTable类
export interface ColumnDefinition<?> {
readonly name: ?;
title: string;
align?: 'left' | 'center' | 'right';
sortable?: boolean;
hideable?: boolean;
// ...
}
export interface DataTableOptions<?> {
readonly columnDefinitions: ColumnDefinition<?>[];
// ...
}
export class DataTable<?> {
private readonly columnDefinitions: ReadonlyArray<ColumnDefinition>;
constructor(options: DataTableOptions<?>) {
this.columnDefinitions = options.columnDefinitions;
// ...
}
// ...
public title(columnName: ?): string {
return this.columnDefinitions.find(({ name }) => name === columnName)?.title ?? '';
}
// ...
}
我把一些?
放在我不知道如何提供泛型类型的地方
目标是调用以下内容
const table = new DataTable({
columnDefinitions: [
{ name: 'id', title: 'ID' },
{ name: 'v1', title: 'Value 1' },
{ name: 'v2', title: 'Value 2' }
],
// ...
});
然后,表的键入应如下所示:DataTable<'id' | 'v1' | 'v2'>
如果有人尝试使用具有相同名称的多个列定义,则会发生错误
此外,一些成员职能也应从中受益
table.title('id'); // 'ID'
table.title('test'); // compile time error
确保我们捕获传递的实际名称很容易。我们将以元组类型捕获整个列定义(这在尝试测试唯一性时也将为我们提供帮助)
要捕获columnDefinitions
作为元组类型的类型,我们需要一个type参数,让其C
以约束进行调用[ColumnDefinition] | ColumnDefinition[]
。第一个[ColumnDefinition]
确保我们得到一个元组类型,而ColumnDefinition[]
确保我们允许任何类型的元组。
为了捕获每个属性的名称,我们需要做更多的事情。首先ColumnDefinition
需要一个类型参数扩展string
(interface ColumnDefinition<N extends string>{...}
)。这将使我们能够编写类似ColumnDefinition<'id'>
有可能保留名称的字符串文字类型,ColumnDefinition
我们需要回到的约束C
。现在,我们需要在约束中指定类型参数。[ColumnDefinition<string>] | ColumnDefinition<string>[]
是有效的,但不会捕获字符串文字类型,只会推断出string
元组中的所有项目。为了获得字符串文字类型,我们需要将一个额外的类型参数限制为string
(这将触发编译器保留字符串文字类型。
所以最终的定义DataTable
是class DataTable<C extends [ColumnDefinition<V>] | ColumnDefinition<V>[], V extends string> {... }
有了C
类型后,我们可以输入title
相对于的参数C
。所以我们可以写title(columnName: C[number]['name']): string
唯一性部分难以保证。我们将需要一个递归条件类型(附加了各种警告)。但这是可以完成的。如果没有重复项或包含自定义错误消息的元组,则该IsUnique
类型下面将返回{}
,这将在调用构造函数时导致错误。
产生的解决方案:
export interface ColumnDefinition<N extends string> {
readonly name: N;
title: string;
align?: 'left' | 'center' | 'right';
sortable?: boolean;
hideable?: boolean;
// ...
}
export interface DataTableOptions<C extends ColumnDefinition<string>[]> {
readonly columnDefinitions: C;
// ...
}
type ColumnName<T> = T extends ColumnDefinition<infer N> ? N : never;
type IsUnique<T extends any[], E = never> = {
next: ((...a: T) => void) extends ((h: infer H, ...t: infer R) => void) ?
[ColumnName<H>] extends [E] ? ["Names are not unique", ColumnName<H>, "was found twice"] :
IsUnique<R, E | ColumnName<H>>: ["NO", T]
stop: {}
}[T extends [] ? "stop" : "next"];
export class DataTable<C extends [ColumnDefinition<V>] | ColumnDefinition<V>[], V extends string> {
private readonly columnDefinitions: Readonly<C>;
constructor(options: DataTableOptions<C> & IsUnique<C> ) {
this.columnDefinitions = options.columnDefinitions;
// ...
}
public title(columnName: C[number]['name']): string {
return this.columnDefinitions.find(({ name }) => name === columnName)?.title ?? '';
}
// ...
}
const table = new DataTable({
columnDefinitions: [
{ name: 'id', title: 'ID' },
{ name: 'v1', title: 'Value 1', align: "right" },
{ name: 'v2', title: 'Value 2', align: "right" }
// { name: 'id', title: 'ID' },
// Comment the line above to get the error below
// Type '{ columnDefinitions: [{ name: "id"; title: string; }, { name: "v1"; title: string; }, { name: "v2"; title: string; }, { name: "id"; title: string; }]; }' is not assignable to type '["Names are not unique", "id", "was found twice"]'.(2345)
],
// ...
});
table.title("Id") // err
table.title("id") // ok
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句