如何在Typescript中强制对象属性之间的动态匹配?

hgtggwj0  于 2023-06-07  发布在  TypeScript
关注(0)|答案(1)|浏览(160)

我有一个对象,它指定了一些属性(props),还有一个数组,描述了它们在网格中的组织方式(order)。
顺序数组中位置表示网格的一行。如果某个索引处的值包含某个属性的名称,则它声明该属性应占据整行,而包含属性名称元组的值则声明这些属性应并排显示。
下面是一个用例:

const a: OrderedProperties = {
  props: ['title', 'firstName', 'lastName', 'nickName'],
  order: [
    'title',
    ['firstName', 'lastName'],
    'nickName'
  ]
}

但是,当前的 OrderedProperties 类型不会检查order数组中的值是否与props数组中的属性名匹配:

export type OrderGrid = Array<string | [string, string]>;

export type OrderedProperties = {
  props: string[];
  order: OrderGrid
};

我怎样才能让typescript动态地检查传递到order字段中的字符串是否与props数组中的属性名匹配?

nbysray5

nbysray51#

您可以使用generic类型参数来约束每个属性允许的字符串文字:

type OrderGrid<S extends string> = Array<S | [S, S]>;

type OrderedProperties<P extends string, O extends P = P> = {
  props: P[];
  order: OrderGrid<O>;
};

然后,您可以在类型注解中指定允许的字符串字面量(无关值将产生诊断错误):

const a: OrderedProperties<"firstName" | "lastName" | "nickName" | "title"> = {
  props: ["title", "firstName", "lastName", "nickName"],
  order: [
    "title",
    ["firstName", "lastName"],
    "nickName",
  ],
};

const a2: OrderedProperties<"firstName" | "lastName" | "nickName"> = {
  props: ["title", "firstName", "lastName", "nickName"], /* Error (expected)
          ~~~~~~~
  Type '"title"' is not assignable to type '"firstName" | "lastName" | "nickName"'.(2322) */
  order: [
    "title", /* Error
    ~~~~~~~
    Type '"title"' is not assignable to type '"firstName" | "lastName" | "nickName" | ["firstName" | "lastName" | "nickName", "firstName" | "lastName" | "nickName"]'.(2322) */
    ["firstName", "lastName"],
    "nickName",
  ],
};

需要枚举类型注解中允许的字符串文字可能会很繁琐。如果对象类型是要在函数中使用的,你可以简单地用props中的字符串约束order中的字符串,编译器可以为你推断:

declare function handleOrderedProps<P extends string, O extends P = P>(
  props: OrderedProperties<P, O>,
): void;

handleOrderedProps({
  props: ["title", "firstName", "lastName", "nickName"],
  order: [
    "title",
    ["firstName", "lastName"],
    "nickName",
  ],
}); // ok

handleOrderedProps({
  props: ["title", "firstName", "lastName", "nickName"],
  order: [
    "title",
    ["firstName", "lastName"],
  ],
}); // "nickName" not present (ok)

handleOrderedProps({
  props: ["title", "firstName", "lastName"],
  order: [
    "title",
    ["firstName", "lastName"],
    "nickName", /* Error (expected)
    ~~~~~~~~~~
    Type '"nickName"' is not assignable to type '"firstName" | "lastName" | "title" | ["firstName" | "lastName" | "title", "firstName" | "lastName" | "title"]'.(2322) */
  ],
});

正如您和@jcalz所讨论的,其中一个属性是多余的-但我只能推测是哪一个。
与平面数组(props属性)相比,元组组(order属性)结构化的数据中可能存在语义信息,您可以使用Array.prototype.flat()简单地平面化嵌套数组:

function getPropsFromOrder<S extends string>(order: OrderGrid<S>): S[] {
  return order.flat() as S[];
}

TS Playground中的代码

相关问题