我想创建一个类型谓词,以便当我在泛型的Union数组上使用filter时,它将返回正确的类型.

我编写了一个类型来指示相应id的转换函数的输入/输出类型:

type ConverterGroupIdToFunc = {
  text: [string, string]
  image: [File, string]
}

type ConverterGroupId = keyof ConverterGroupIdToFunc

type ConverterGroup<T extends ConverterGroupId> = {
  id: T
  convertFunc: (s: ConverterGroupIdToFunc[T][0]) => (ConverterGroupIdToFunc[T][1]);
}

我还需要一个ConverterGroup数组来存储2种不同类型的泛型(ConverterGroup<"text">|ConverterGroup<"image">)[]

type ConverterGroupUnion = ConverterGroup<"text"> | ConverterGroup<"image">

对于converterGroups: (ConverterGroup<"text">|ConverterGroup<"image">)[],如果转换器组的id也是"text",我希望find()返回类型ConverterGroup<"text">.

我试图使用一个类型谓词告诉find()返回正确的类型,但找不到方法:

function isConverterGroup<T extends ConverterGroupId>(converterGroupId: T) {
  return (converterGroup: ConverterGroupUnion): converterGroup is ConverterGroup<T> => {
    return converterGroup.id === converterGroupId;
  }
}

const convertFunc = converterGroups.find(isConverterGroup("text"))!.convertFunc;

上面的代码给了我一个错误:

A type predicate's type must be assignable to its parameter's type.
  Type 'ConverterGroup<T>' is not assignable to type 'ConverterGroupUnion'.
    Type 'ConverterGroup<T>' is not assignable to type 'ConverterGroup<"text">'.
      Types of property 'id' are incompatible.
        Type 'T' is not assignable to type '"text"'.ts(2677)

这是Typescript Playground Link

推荐答案

有时你知道类型X可以分配给类型Y,但TypeScript看不到它,因为XY依赖于某个类型generic. 在你的例子中,ConverterGroup<T>是泛型,编译器无法确定它是否可分配给ConverterGroupUnion.由于custom type guard function要求y is X的类型X可分配给typeof y,所以它抱怨converterGroup is ConverterGroup<T>.

在这样的情况下,通常可以根据用例将X更改为X & YExtract<X, Y>Extract<Y, X>来修复它.intersection总是可分配给它的成员,union过滤Extract utility type的结果也是如此. 如果你认为X是可以分配给Y的,那么一旦指定泛型,最终X & YExtract<X, Y>(可能还有Extract<Y, X>,取决于YX的形式)将只是X,并且结果行为不会改变.如果你在可赋值性上错了,那么你最终会得到其他东西,可能是never,所以你应该小心,知道你在用这些类型做什么.

无论如何,因为在你的例子中,看起来你只是想过滤ConverterGroupUnion到可分配给ConverterGroup<T>的成员,你可以使用Extract<ConverterGroupUnion, ConverterGroup<T>>:

function isConverterGroup<T extends ConverterGroupId>(converterGroupId: T) {
  return (converterGroup: ConverterGroupUnion):
    converterGroup is Extract<ConverterGroupUnion, ConverterGroup<T>> => {
    return converterGroup.id === converterGroupId;
  }
}

现在没有错误,您的其他代码可以正常工作:

const convertFunc = converterGroups.find(isConverterGroup("text"))!.convertFunc;
// const convertFunc: (s: string) => string

请注意,如果你做了一些奇怪的事情,

const x = isConverterGroup(Math.random() < 0.5 ? "text" : "image");
// const x: (converterGroup: ConverterGroupUnion) => converterGroup is never

你得到了我提到的never,因为ConverterGroup<"text" | "image">ConverterGroup<"text">ConverterGroup<"image">都没有关系,因为ConverterGroup<T>等于T中的invariant(见Difference between Variance, Covariance, Contravariance, Bivariance and Invariance in TypeScript). 这也是错误的一部分原因.

Playground link to code

Typescript相关问答推荐

界面中这种类型的界面界面中的多态性

了解TS类型参数列表中的分配

ANGLE找不到辅助‘路由出口’的路由路径

是否有一个版本的联合类型递归地使属性只出现在一个可选选项中?

缩小并集子键后重新构造类型

如何使用类型谓词将类型限制为不可赋值的类型?

被推断为原始类型的交集的扩充类型的交集

剧作家元素发现,但继续显示为隐藏

是否可以判断某个类型是否已合并为内部类型?

如何将TS泛型用于react-hook-form接口?

是否可以通过映射类型将函数参数约束为预定义类型?

使用或属性编写无限嵌套的接口

错误TS7030:并非所有代码路径都返回值.";由tsfig.json中的";Strong";:False引起

有没有一种方法可以提升对象的泛型级别,而对象的值是泛型函数?

在tabel管理区域react js上设置特定顺序

如何避免TS2322;类型any不可分配给类型never;使用索引访问时

我可以在 Typescript 中重用函数声明吗?

如何从 Select 元素中删除选项

有没有办法将类型与关键更改相匹配?

从平面配置推断嵌套递归类型