首页 > 解决方案 > 从受约束的泛型参数中提取数据属性名称

问题描述

我们有一个泛型类型,它成功地从它接收的类型中提取数据属性。它在操场上,它的工作原理是这样的。

type DataPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];

type FooBarBaz = {
  foo: string;
  bar: Function;
  baz: number;
};

const f1 = () => {
  type DataProps = DataPropertyNames<FooBarBaz>;

  const x: DataProps = 'foo'; // good, no error, because foo is a string

  const y: DataProps = 'bar'; // good, expected error, because bar is a Function.

  return {
    x,
    y
  };
};

上面的例子展示了DataPropertyNames当我们传递一个具体类型比如FooBarBaz. 正如预期的那样,它丢弃了作为函数的道具,并保留了不是函数的道具。

我们的问题是为什么以下内容不能按预期工作。我们期望它DataProps会接受'foo'字符串,因为Textends FooBarBaz

const f2 = <T extends FooBarBaz>() => {
  type DataProps = DataPropertyNames<T>;

  const x: DataProps = 'foo'; // bad, unexpected error

  const y: DataProps = 'bar';

  return {
    x,
    y
  };
};

为什么 TypeScript 不能找出T至少与它扩展的类型具有相同的数据属性名称?如果有的话,我们如何说服 TypeScript 呢?

标签: typescriptgenericstypes

解决方案


Mu-Tsan Tsai 的答案是正确的,我只是想对其进行扩展,以明确简单的泛型类型确实有效。我一直对这种情况感到好奇,我认为提供一个实验来观察类型何时变得过于复杂很有用。您可以将下面的示例(通过操场链接)与未来版本的 typescript 一起使用,以查看情况是否有所改善(我的实验在 TS 4.0.2 中运行)。

在下面的代码中,当泛型 T 直接与扩展约束一起使用时,我们可以访问泛型约束中使用的类的属性。这是好的和预期的,不足为奇。当你有一个非常简单的泛型类型(basicGenType)时,它也可以工作,在我的例子中,我使用了 keyof 运算符并且泛型类型能够从 T 中提取键。实际上我对此有点惊讶,因为我想使用 T 作为泛型参数时,任何泛型类型都会失败。对于稍微复杂的基本类型,它会失败(basicGenType2、basicGenType3)。似乎一旦我开始推断一种新类型(在本例中为 K)并使用它来组成作为断点的结果类型。一个有趣的观察是智能感知自动完成 basicGenType2 和 basicGenType3 的关键字段。

一旦我测试了扩展并推断它也失败了,这并不奇怪,它符合您的经验。

type basic = {simple:number}
type basicGenType<T> = keyof T
type basicGenType2<T> = {[K in keyof T]: K}
type basicGenType3<T> = {[K in keyof T]: T[K]}
type complexGenType1<T> = T extends basic ? number : never
type complexGenType2<T> = T extends {simple:infer R} ? R : never

const g = <T extends basic>(a:T):void => {

  //directly on T typescript can use the properties of basic
  a.simple = 5; //GOOD --typescript is good with this
  a.simple = "5" //GOOD -- typescript complains 
  a.other = 5;  //GOOD -- typescript complains

  //for a simple generic type typescript still works
  //the resulting type should only allow the value 'simple'
  type basicType = basicGenType<T>
  let b:basicType = 'simple'; //GOOD -- typescript is ok with this
  b = 'other'; //GOOD -- typescript complains

  //for a slightly more complicated simple type we fail
  // the resulting type should only allow the value {simple:'simple'}
  type basicType2 = basicGenType2<T>
  let b2:basicType2 = {simple:'simple'}; //BAD -- typescript fails even though it should be ok with this

   //for a slightly more complicated simple type we fail
  // the resulting type should only allow the value {simple:number}
  type basicType3 = basicGenType3<T>
  let b3:basicType3 = {simple:3}; //BAD -- typescript fails even though it should be ok with this

  //we've introduced extends here for complexity
  //complexType shoud resolve to number here
  type complexType1 = complexGenType1<T>;
  let c:complexType1 = 5;//BAD -- typescript fails even though it should be ok with this

  //we've introduced extends and infer here for even more complexity
  //complexType2 shoud resolve to number here
  type complexType2 = complexGenType2<T>;
  let d:complexType2 = 5;//BAD -- typescript fails even though it should be ok with this
}

游乐场链接


推荐阅读