强制执行泛型类型的类型安全性

时间:2018-03-17 18:04:43

标签: typescript typescript-generics

如何使用泛型来强制实现某种特定类型的值?

// 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抱怨后者?

1 个答案:

答案 0 :(得分:0)

我想你想这样输入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允许您在通用参数中执行一些“循环”类型约束。因此,在这种情况下,我们不需要指定V2(因为它没有做任何工作),我们只需要T extends Record<K2, string>,这意味着类型T必须有{ {1}}键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''shouldNotWork'`。哦,好吧。)

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