首页 > 解决方案 > 根据其他属性更改接口属性的类型

问题描述

我正在处理网格(在 React 中),我有以下类型和接口(您可以在此处加载代码):

export type DataGridColumnType = 'currency' | 'date' | 'number' | 'text';

interface CurrencyColumnOptions {
  symbol: string;
}

interface DateColumnOptions {
  format: string;
}

interface NumberColumnOptions {
  decimalPoint: string;
  thousandSeparator: string;
}

type TextColumnOptions = {};

export interface DataGridColumn {
  field: string;
  label: string;
  title: string;
  columnOptions?: {}; // either CurrencyColumnOptions, DateColumnOptions, NumberColumnOptions or TextColumnOptions based on the "type" property
  type?: DataGridColumnType;
}

export interface DataGrid {
  columns: DataGridColumn[];
  rows: Record<string, unknown>[];
}

const column: DataGridColumn = {
  field: 'id',
  label: 'ID',
  title: 'ID',
  type: 'date',
  columnOptions: {
    format: 'YYYY-mm-dd',
  },
};

我想根据 的值更改属性columnOptions的类型,以便当“类型”为“日期”时,它将使用 DateColumnOptions属性。DataGridColumntypecolumnOptions

我知道我可以在这里创建一个泛型,但是这些接口在我的网格系统内部有几层,拥有一个泛型会使使用网格变得更加复杂。

我环顾四周,但我不确定要搜索的正确术语。

这是可以做到的吗?还是我必须创建多个接口并使用联合类型?

谢谢

标签: typescript

解决方案


当您想以某种方式约束一个对象类型的两个或多个属性时,您需要使该对象类型成为可接受配置的联合,或者使受约束的属性以某种方式依赖于泛型类型参数的泛型类型.

在您的示例中,该type属性是一个字符串文字,您可以使用它来确定该columnOptions属性的类型。这是判别联合的理想用例,type作为判别式


第一:aninterface不能是联合类型;因此,如果您想DataGridColumn成为受歧视的工会,则需要将其设为type别名这可能很好,但是aliases 和s之间有一些区别typeinterface

现在您可以手动显式地写出DataGridColumn联合,但这会很烦人且多余:

// Don't do this
type DataGridColumn = {
    field: string;
    label: string;
    title: string;
    type: "number";
    columnOptions?: {
        decimalPoint: string;
        thousandSeparator: string;
    };
} | {
    field: string;
    label: string;
    title: string;
    type: "currency";
    columnOptions?: {
        symbol: string;
    } 
} | { // sigh, I'm bored, forget it

相反,您可以进行一些重构并使用一些类型操作来让编译器DataGridColumn根据一些非冗余类型以编程方式定义:

首先,我们可以为真正常见的字段创建一个接口:

interface BaseDataGridColumn {
  field: string;
  label: string;
  title: string;
}

然后我们可以定义我们用作键和值之间typecolumnOptions位置之间的映射:typecolumnOptions

interface DataGridColumnOptions {
  currency: {
    symbol: string;
  },
  date: {
    format: string;
  }
  ,
  number: {
    decimalPoint: string;
    thousandSeparator: string;
  },
  text: {}
}

请注意,我们不打算拥有 type 的值DataGridColumnOptions;我们将使用它来定义DataGridColumn

type DataGridColumnType = keyof DataGridColumnOptions;

type DataGridColumn = { [K in DataGridColumnType]: BaseDataGridColumn & {
  type: K,
  columnOptions?: DataGridColumnOptions[K]
}}[DataGridColumnType]

首先,我们DataGridColumnType通过获取 的键重新创建您的DataGridColumnOptions,然后我们通过映射属性来生成DataGridColumn联合。我们将每个键分配为属性,将每个值分配为属性。我们将这对属性与接口相交以获得扩展类型(相交的行为类似于扩展接口,但它以编程方式工作)。然后我们索引到这个映射类型,将映射类型转换为我们想要的可区分联合。DataGridColumnTypeKtypeDataGridColumnOptions[K]columnOptionsBaseDataGridColumnDataGridColumnType

您可以验证这是否按需要工作:

const column: DataGridColumn = {
  field: 'id',
  label: 'ID',
  title: 'ID',
  type: 'date',
  columnOptions: {
    format: 'YYYY-mm-dd',
  },
}; // okay

const badColumn: DataGridColumn = {
  field: 'id',
  label: 'ID',
  title: 'ID',
  type: 'currency',
  columnOptions: {
    format: 'YYYY-mm-dd', // error!
    //~~~~~~~~~~~~~~~~~~
    //Type '{ format: string; }' is not assignable 
    //to type '{ symbol: string; }'.
  },
}; 

这是我可能会在这里采取的主要方法。如果这对你有用,那就太好了。您可能还需要进行一些其他更改和重构以简化或以不同方式显示内容,但除非按下,否则我不会沿着这条路线走得更远。

Playground 代码链接


推荐阅读