目前,无论是安全还是符合人体工程学的输入代码,都超出了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
这也很有效,但相当笨拙,特别是因为您必须创建In
和Out
的虚拟关键点,这两个关键点没有任何作用.如果你有某种方法来区分你的工厂unions ,它会更符合人体工程学.尽管编译器允许这是类型安全的,但在TypeScrip的类型系统中存在已知的和有意的安全漏洞,您可以很容易地用MICROSOFT/TYPESCRIPT#47109来解决这些漏洞,参见microsoft/TypeScript#48730.
因此,这让我无法提出任何建议,而不仅仅是"使用type assertions和/或the any
type,不要试图从Typescript 中获得超出其处理能力的东西."
Playground link to code个