给定可由函数runFactory: <A, B>(f: Factory<A, B>): B处理的类型Factory<A, B>,如何将runFactory函数应用于由生成函数提供的值,该生成器函数生成类型化联合的工厂值,例如Factory<number, string> | Factory<string, number>

以下代码失败:(playground link)

type Factory<A, B> = { makeValue: () => A, process: (a: A) => B }

const createFactory = (): Factory<string,number> | Factory<number,string> =>
  Math.random() < 0.5 ? 
    { makeValue: () => document.title, process: (s: string) => s.length} : 
    { makeValue: () => Math.random(), process: (n: number) => `${n}`}

const runFactory = <A, B>({makeValue, process}: Factory<A, B>): B => process(makeValue())

console.log(runFactory(createFactory()))

错误消息:

Argument of type 'Factory<string, number> | Factory<number, string>' is not assignable to parameter of type 'Factory<string, number>'.
  Type 'Factory<number, string>' is not assignable to type 'Factory<string, number>'.
    The types returned by 'makeValue()' are incompatible between these types.
      Type 'number' is not assignable to type 'string'.

我找不到一种方法将泛型参数传递给runFactory来进行类型判断(它运行得很好).

  • 我不想在runFactory中使用类型保护,因为我不知道泛型参数-Factory类型是自包含的!
  • 我希望保持runFactory强类型,所以使用anyunknown是不可能的.

请注意,只有一个泛型参数的函数可以很好地工作,大约runFactory<string | number>(createFactory())个就可以了,但我需要both个泛型参数.

推荐答案

目前,无论是安全还是符合人体工程学的输入代码,都超出了Tyescrip的能力范围.TypeScrip并没有真正将其分析结果发布到任意union types页上.

为了让它无缝工作,TypeScrip需要直接支持所谓的existentially quantified generic types,而TypeScrip(坦率地说,其他大多数带有泛型的语言)只有universally个量化的泛型.也就是说,你需要能够说"有一些类型的A这样的……"而不是"所有类型A..."

如果我们有这样的类型(假设您以假设的关键字exists作为类型参数声明的前缀),那么每个Factory<A, B>将自动被认为是可赋值给SomeFactory<B>的,其定义如下:

// THIS IS NOT VALID TS, DO NOT TRY THIS:
type SomeFactory<B> = <exists A>Factory<A, B>;

然后你需要做的就是写下

const runSomeFactory = <B,>(someFactory: SomeFactory<B>): B =>
  someFactory(({ makeValue, process }) => process(makeValue()));

(因为runSomeFactory()实际上并不关心您传入的which A;只关心可以工作的there exists some A),并且您可以使用一个类型参数来调用它,如下所示:

console.log(runSomeFactory<string | number>(createSomeFactory()));

这一切都是安全的和符合人体工程学的.

但这不是语言的一部分.有一个长期开放的功能需求microsoft/TypeScript#14466美元,所以如果它实现了,我们可以重新考虑这个问题,并给它一个更令人满意的答案.

但就目前而言,我们所能做的就是绕开它,否则就放弃.


一种可能的解决方法是try 将存在泛型编码为类似Promise的控制流反转:

type SomeFactory<B> = <R, >(cb: <A>(f: Factory<A, B>) => R) => R;

基本上,SomeFactory<B>是将Factory<A, B>放在一个框中,您可以将回调函数反馈到框中,并返回您喜欢的任何类型的结果,其中类型A从外部完全看不到.您可以将任何Factory打包为SomeFactory:

const someFactory = <A, B>(f: Factory<A, B>): SomeFactory<B> => cb => cb(f);

但你的createFactory()需要返回一个SomeFactory的并集,它才会有用:

const createSomeFactory = () =>
  Math.random() < 0.5 ?
    someFactory({ makeValue: () => document.title, process: (s: string) => s.length }) :
    someFactory({ makeValue: () => Math.random(), process: (n: number) => `${n}` })

然后runSomeFactory

const runSomeFactory = <B,>(someFactory: SomeFactory<B>): B =>
  someFactory(({ makeValue, process }) => process(makeValue()));

在这里,您将旧的runFactory()实现作为回调传入.您可以安全地将createSomeFactory()的输出从SomeFactory<string> | SomeFactory<number>扩大到SomeFactory<string | number>:

console.log(runSomeFactory<string | number>(createSomeFactory()));

万岁.这是类型安全的,但不符合人体工程学.


处理任何特定的Factory类型联合的另一种可能的解决方法是try 使用在microsoft/TypeScript#47109中实现的对correlated unions(在microsoft/TypeScript#30581中定义)的支持.

你可以用Factory<string, number> | Factory<number, string>来表示两个基接口上的distributive object type:

interface In { a: string, b: number }
interface Out { a: number, b: string }
type Fact<K extends keyof In = keyof In> = 
  { [P in K]: Factory<In[P], Out[P]> }[K]

根据上述定义,Fact<"a">Factory<string, number>,Fact<"b">Factory<number, string>,Fact<"a" | "b">是并集Factory<string, number> | Factory<number, string>:

type F = Fact;
// type F = Factory<string, number> | Factory<number, string>

然后,您可以编写一个覆盖In/Out键的泛型函数并调用它:

function runFact<K extends keyof In = keyof In>(f: Fact<K>): Out[K] {
  return runFactory(f); // okay
}
console.log(runFact(createFactory())); // okay

这也很有效,但相当笨拙,特别是因为您必须创建InOut的虚拟关键点,这两个关键点没有任何作用.如果你有某种方法来区分你的工厂unions ,它会更符合人体工程学.尽管编译器允许这是类型安全的,但在TypeScrip的类型系统中存在已知的和有意的安全漏洞,您可以很容易地用MICROSOFT/TYPESCRIPT#47109来解决这些漏洞,参见microsoft/TypeScript#48730.


因此,这让我无法提出任何建议,而不仅仅是"使用type assertions和/或the any type,不要试图从Typescript 中获得超出其处理能力的东西."

Playground link to code

Typescript相关问答推荐

在Angular中,如何在文件上传后清除文件上传文本框

使用Angular和API请求在CRUD操作后更新客户端数据的良好实践

需要使用相同组件重新加载路由变更数据—React TSX

在TypeScript中,除了映射类型之外还使用的`in`二进制运算符?

类型{}上不存在属性&STORE&A;-MobX&A;REACT&A;TYPE脚本

角效用函数的类型推断

如何 bootstrap TS编译器为类型化属性`T`推断正确的类型?

笛卡尔产品类型

从非文字数组(object.keys和array.map)派生联合类型

如何正确地将元组表格输入到对象数组实用函数(如ZIP)中,避免在TypeScrip 5.2.2中合并所有值

VUE 3类型';字符串';不可分配给类型';参考<;字符串&>

我可以将一个类型数组映射到TypeScrip中的另一个类型数组吗?

如何在TypeScript中将元组与泛型一起使用?

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

对象只能使用 Typescript 中另一个对象的键

如何在 TypeScript 类型定义中简化定义长联合类型

基于泛型类型的条件类型,具有条目属性判断

在对象类型的类型别名中,属性是用分号 (;) 还是逗号 (,) 分隔的?

Typescript 编译错误:TS2339:类型string上不存在属性props

使用 TypeScript 在 SolidJS 中绘制 D3 力图