typescript - 推断对象数组中特定键的值

标签 typescript

我想编写一个非常智能的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 的类型作为元组类型,我们需要一个类型参数,我们称之为 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


Playground Link

关于typescript - 推断对象数组中特定键的值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58949601/

相关文章:

typescript - 如何在带有 typescript 的 vue.js 中使用 javascript 组件?

javascript - 在 SWR 中使用 mutate 重新验证数据 - 我应该使用哪个?

Angular Material 日期选择器 : Date Parsing UTC problem 1 day before

javascript - 测试变量类型或抛出 TypeScript 的函数?

javascript - 查找已登录 Angular 6 的用户 ID

typescript :是否有可能获得函数的参数列表类型?

node.js - extraHeaders 未附加到请愿书上

javascript - vscode : rearrange code for js/ts files

css - Jest 在 typescript 中遇到了意外的标记

angular - Ionic - ngStyle 中来自 SASS 文件的自定义颜色