首页 文章

从第一个自动推断第二个通用参数

提问于
浏览
1

我有以下接口来定义一个表的列:

export interface IColumnDefinition<TRow, TField extends keyof TRow> {
    field: TField;
    label?: string;
    formatter?: (value: TRow[TField], row: TRow) => string;
}

现在我想要的只是提供行的类型( TRow ),并让TypeScript根据 field 属性中的值自动推断字段的类型( TField ) .

现在假设我的行有以下界面:

interface User {
    name: string;
    birthDate: number;
}

我尝试的是以下内容:

const birthDateColumnDefinition: IColumnDefinition<User> = {
    field: 'birthDate',
    formatter: value => new Date(value).toDateString(),
}

这给了我以下错误:

泛型类型'IColumnDefinition <TRow,TField extends keyof TRow>'需要2个类型的参数 .

我还尝试使用函数来创建定义,希望可以从参数推断出类型:

function createColumnDefinition<TField extends keyof TRow>(
    field: TField,
    columnDef: Partial<IColumnDefinition<TRow, TField>>): IColumnDefinition<TRow, TField>
{
    return {
        ...columnDef,
        field,
    };
}

const birthDateColumnDefinition = createColumnDefinition<User>('birthDate', {
    formatter: (value, row) => new Date(value).toDateString(),
});

这给了我一个类似的错误:

例外2个类型参数,但得到1 .

但是,如果我还将行作为参数包含在内,并将所有泛型参数一起删除,则可以正常工作:

function createColumnDefinition<TRow, TField extends keyof TRow>(
    row: TRow,
    field: TField,
    columnDef: Partial<IColumnDefinition<TRow, TField>>): IColumnDefinition<TRow, TField>
{
    return {
        ...columnDef,
        field,
    };
}

const user: User = {
    name: 'John Doe',
    birthDate: 549064800000,
};

const birthDateColumnDefinition = createColumnDefinition(user, 'birthDate', {
    formatter: (value, row) => new Date(value).toDateString(),
});

这确实有效,但这不是一个选项,因为我在定义列时实际上没有一行 .

那么有没有办法使这项工作(最好是通过使用接口,而不是一个功能)?

2 回答

  • 1

    现在我想要的只是提供行的类型(TRow),并让TypeScript根据field属性中的值自动推断字段的类型(TField) .

    这看起来非常类似于partial type inference,在typescript中目前不支持,并且不清楚这个特定用例是否会支持它 .

    但是有一件事你可以做 - 你可以有一个函数接受 TRow 的显式类型参数,并返回另一个函数,它将从其参数推断 TField . 语法有点笨拙,但它有效:

    function columnDefinition<TRow>(): 
       <TField extends keyof TRow>(def: IColumnDefinition<TRow, TField>) => 
            IColumnDefinition<TRow, TField> {
        return  <TField extends keyof TRow>(def: IColumnDefinition<TRow, TField>) => def
    }
    
    export interface IColumnDefinition<TRow, TField extends keyof TRow> {
        field: TField;
        label?: string;
        formatter?: (value: TRow[TField], row: TRow) => string;
    }
    
    interface User {
        name: string;
        birthDate: number;
    }
    

    要使用它,不使用任何参数调用 columnDefinition<User>() ,然后立即使用列定义对象作为参数调用返回的函数:

    const birthDateColumnDefinition = columnDefinition<User>()({
        field: 'birthDate',
        formatter: value => new Date(value).toDateString(), 
                // value inferred as (parameter) value: number
    });
    
  • 0

    我最终使用artem的答案来编写列定义构建器类:

    export class ColumnDefinitionBuilder<TRow> {
    
      private _columns: Array<IColumnDefinitionWithField<TRow, any>> = [];
    
      public define<TValue>(field: ((row: TRow) => TValue) | string, def: IColumnDefinition<TRow, TValue>)
        : ColumnDefinitionBuilder<TRow> {
        this._columns.push({...def, field});
        return this;
      }
    
      public get columns() {
        return this._columns;
      }
    }
    

    这允许使用以下语法来定义列:

    const userColumnDefinitions = new ColumnDefinitionBuilder<User>()
        .define(x => x.birthDate, {
            label: 'Birth date',
            formatter: x => new Date(x).toDateString(),
        })
        .define(x => x.name, {
            label: 'Name',
        })
        .columns;
    

相关问题