从数组的泛型推断正确类型的问题

时间:2019-08-28 21:11:41

标签: typescript generics type-safety

在下面的示例中,我很难弄清楚如何缩小推断的类型。目前,所有格式函数都接受string | number | boolean并期望返回string | number | boolean

理想情况下,我想根据typeID的值将它们缩小为仅一种类型。

enum TypeID {
  Number = "__number__",
  String = "__string__",
  Boolean = "__boolean__"
}

type Type<TYPE_ID extends TypeID> = {
  [TypeID.Number]: number;
  [TypeID.String]: string;
  [TypeID.Boolean]: boolean;
}[TYPE_ID];

type Item<
  TYPE_ID extends TypeID,
  TYPE extends Type<TYPE_ID> = Type<TYPE_ID>
> = {
  typeID: TypeID;
  format: (input: TYPE) => TYPE;
};

type Options<TYPE_ID extends TypeID> = Array<Item<TYPE_ID>>;

const someFunc = <TYPE_ID extends TypeID>(options: Options<TYPE_ID>) => {
  return null as any;
};

someFunc([
  {
    typeID: TypeID.Number,
    format: input => input // these should have type "number"
  },
  {
    typeID: TypeID.Boolean,
    format: input => input // these should have type "boolean"
  },
  {
    typeID: TypeID.String,
    format: input => input // these should have type "string"
  }
]);

1 个答案:

答案 0 :(得分:1)

使用类似的类型名称(TYPE vs Type vs TYPE_ID vs TypeID真是令人困惑,因此在以下内容中,我将使它们的名称不同...对于通用参数,我使用一两个大写字母(无论出于何种原因,这都是惯例)。这是Type

type Type<T extends TypeID> = {
  [TypeID.Number]: number;
  [TypeID.String]: string;
  [TypeID.Boolean]: boolean;
}[T];

这是Item

type Item<T extends TypeID> = {
  typeID: T; // <-- T, not TypeID
  format: (input: Type<T>) => Type<T>;
};

请注意,您的示例对此做了进一步修改。您在此处有两个类型参数,但似乎仅对第二个使用默认值,因此我将其删除并使用了该默认值。但是这里重要的修改是typeID属性是泛型类型T,而不是 concrete 类型TypeID。我认为您可能打算在您的代码中执行此操作,但是也许很难看到其中的区别。


现在,我要从这里开始的方法是生成一个名为SomeItem的类型,该类型是所有可能的Item<T>类型的并集。这种具体类型比通用类型更容易使用:

type _SomeItem<T extends TypeID> = T extends any ? Item<T> : never;
type SomeItem = _SomeItem<TypeID>;
// type SomeItem = Item<TypeID.Number> | Item<TypeID.String> | Item<TypeID.Boolean>

通过在_SomeItem的定义中使用distributive conditional type,将Item<T>分布在TypeID个值的并集上来进行工作。

最后,someFunc可以是一个具体功能:

const someFunc = (options: Array<SomeItem>) => {
  return null as any;
};

使用它时,您会得到预期的推断:

someFunc([
  {
    typeID: TypeID.Number,
    format: input => input // has type number
  },
  {
    typeID: TypeID.Boolean,
    format: input => input // has type boolean
  },
  {
    typeID: TypeID.String,
    format: input => input // has type string
  }
]);

好的,希望能有所帮助。祝你好运!
Link to code