强制泛型类型值的类型安全

Enforce type safety of a value of a generic type

我如何使用泛型来将值的类型强制为特定类型?

// An example array
const testArr = [
  {
    id: 3,
    name: 'Spaghetto', // NOTE: Type 'string' here
    shouldNotWork: 3.14, // NOTE: Type 'number' here
  },
  {
    id: 5,
    name: 'Bread',
    shouldNotWork: 3.14,
  },
];

这是我试图成为我的映射函数的东西,但我必须附加 as V2 以使 TS 不抱怨:/

type Mapping<T, U> = (val: T, i: number, arr: T[]) => U;

interface Option<T> {
  value: T;
  label: string; // <- NOTE: Type string is required
}

const typeToOption = <
  T,
  K1 extends keyof T,
  K2 extends keyof T,
  V2 extends T[K2] & string // <- NOTE: 'string' union here to match
>(
  valueK: K1,
  labelK: K2,
): Mapping<T, Option<T[K1]>> => (item: T): Option<T[K1]> => ({
  value: item[valueK],
  label: item[labelK] as V2,
});

我希望 TS 允许我这样做

const result = testArr.map(typeToOption('id', 'name'));

...但不是这个

const result = testArr.map(typeToOption('id', 'shouldNotWork'));

如何让 TS 抱怨后者?

我想你想这样输入 typeToOption():

const typeToOption = <
  T extends Record<K2, string>,
  K1 extends keyof T,
  K2 extends keyof T,
>(
  valueK: K1,
  labelK: K2,
): Mapping<T, Option<T[K1]>> => (item: T): Option<T[K1]> => ({
  value: item[valueK],
  label: item[labelK],
});

TypeScript 允许您在泛型参数中做一些 "circular" 类型约束。所以在这种情况下,我们根本不需要指定 V2(因为它没有做任何工作),我们只需要 T extends Record<K2, string>,这意味着类型 T 必须有一个 string 在键 K2 处的值 属性。这足以为您提供所需的行为:

const result = testArr.map(typeToOption('id', 'name')); // okay

const result = testArr.map(typeToOption('id', 'shouldNotWork')); // error
// [ts] Argument of type '"id"' is not assignable to 
// parameter of type '"shouldNotWork"'.

虽然错误有点莫名其妙,但至少是个错误。 (如果重要的话:类型推断失败会导致有趣的事情发生。T 退回到 Record<'shouldNotWork', string>,相当于 {shouldNotWork: string},而 keyof T 只是 'shouldNotWork' ,因此 K1 extends'shouldNotWork'`。好吧。)

希望对您有所帮助;祝你好运!