I've got this interface which will serve as props for a section component displaying different equipment. Each piece of equipment will be placed on a card inside a grid column.
Here's the interface:

interface EquipmentSectionProps {
  bgColor: "black" | "lightGrey";
  columns: number;
  category: string;
  products: string[];
  productImages: string[];
  productReferals: string[];
}

在给定属性列的情况下,我想用属性columns限制数组productsproductImagesproductReferals的大小,因为它们的处置与列数相关.

显然,要try 的第一件事是直接在数组中传递属性列,如下所示:

interface EquipmentSectionProps {
  bgColor: "black" | "lightGrey";
  columns: number;
  category: string;
  products: string[columns];
  productImages: string[columns];
  productReferals: string[columns];
}

然而,返回的响应是

Cannot find name 'columns'.ts(2304) type columns = /*unresolved*/ any

有没有一种更简单的方法来实现这一点,而不需要创建重复的接口并将属性传递给新的接口?

推荐答案

您可以将接口generic设置为columns属性的类型,然后将相应的数组属性定义为适当长度的101.

为此,您需要一个类似TupLen<N extends number, V>的实用程序类型,它将解析为一个长度为N的元组,其中每个元素都是V类型:

interface EquipmentSectionProps<N extends number> {
  bgColor: "black" | "lightGrey";
  columns: N;
  category: string;
  products: TupLen<N, string>;
  productImages: TupLen<N, string>;
  productReferals: TupLen<N, string>;
}

const good: EquipmentSectionProps<3> = {
  bgColor: "black", category: "abc", columns: 3,
  products: ["a", "b", "c"],
  productImages: ["d", "e", "f"],
  productReferals: ["g", "h", "i"]
};

const bad: EquipmentSectionProps<3> = {
  bgColor: "black", category: "abc", columns: 3,
  products: ["a", "b", "c"],
  productImages: ["d", "e"], // error! 2 elements but requires 3
  productReferals: ["g", "h", "i", "j"] // error! 4 elements but only allows 3.
};

但是TupLen<N, V>中没有内置的,你必须自己写.一种方法是使用variadic tuple types作为tail-recursive conditional type:

type TupLen<N extends number, V = any, A extends V[] = []> =
    number extends N ? V[] : N extends A['length'] ? A : TupLen<N, V, [...A, V]>;

type TestNumber4 = TupLen<4, number>;
// type TestNumber4 = [number, number, number, number]
type TestBoolean2 = TupLen<2, boolean>;
// type TestBoolean2 = [boolean, boolean]

此外,如果您不想编写两次列长度,如在const v: EquipmentSectionProps<3> = { columns: 3, ⋯}中(一次用于类型参数,一次用于columns属性),则可以从columns属性的值向infer编写一个Helper函数作为泛型类型参数:

const equipmentSectionProps = 
  <N extends number>(esp: EquipmentSectionProps<N>) => esp;

const good = equipmentSectionProps({
  bgColor: "black", category: "abc",
  columns: 3,
  products: ["a", "b", "c"],
  productImages: ["d", "e", "f"],
  productReferals: ["g", "h", "i"]
})
// const good: EquipmentSectionProps<3>

const bad = equipmentSectionProps({
  bgColor: "black", category: "abc",
  columns: 3,
  products: ["a", "b", "c"],
  productImages: ["d", "e"], // error, 
  // '[string, string]' is not '[string, string, string]'.
  productReferals: ["g", "h", "i", "j"] // error, 
  // '[string, string, string, string]' is not '[string, string, string]'.
})
// const good: EquipmentSectionProps<3>

您无需显式写出EquipmentSectionProps<3>即可获得相同的行为.

Playground link to code

Typescript相关问答推荐

如何在类型脚本中声明对象其键名称不同但类型相同?

为什么在TypScript中写入typeof someArray[number]有效?

如何使类型只能有来自另一个类型的键,但键可以有一个新值,新键应该抛出一个错误

类型脚本类型字段组合

具有继承的基于类的react 组件:呈现不能分配给基类型中的相同属性

在映射类型中用作索引时,TypeScrip泛型类型参数无法正常工作

通过字符串索引访问对象';S的值时出现文字脚本错误

如何在Vue中使用Enum作为传递属性的键类型?

刷新时保持当前名称的Angular

是否可以强制静态方法将同一类型的对象返回其自己的类?

类型与泛型抽象类中的类型不可比较

是否将Angular *ngFor和*ngIf迁移到新的v17语法?

在抽象类构造函数中获取子类型

类型脚本中参数为`T`和`t|unfined`的重载函数

使用强制转换编写打字函数的惯用方法

如何提取具有索引签名的类型中定义的键

传入类型参数<;T>;变容函数

如何让Record中的每个值都有独立的类型?

带有过滤键的 Typescript 映射类型

根据索引将元组拆分为两个块