您实际上不能直接这样做,因为您关心的类型是Val
类型参数的constraint,并且目前没有办法将泛型约束infer.请参见microsoft/TypeScript#41040.
要做到这一点,我能想到的唯一方法是在types和expressions中都经历了很多困难.这些表达式将被发送到JavaScript,因此您将在运行时拥有一些完全没有作用的代码.
首先,让我们从typeof fn
开始,我在这里命名为Fn
:
type Fn = <
Namespace extends keyof ParamMap, Val extends Params<Namespace>
>(namespace: Namespace, key: Val) => string
我们想找出当namespace
是"a"
时所需的key
类型.但打字系统缺乏直接提出这个问题所必需的那种表现力.从概念上讲,您可以想象将Namespace
指定为"a"
,但如果不同时指定Val
(不存在"部分"类型参数推断;请参见microsoft/TypeScript#26242),则无法做到这一点.你也可以试着只写call fn("a", ???)
,但是???
没有什么好填的;如果你填入一些随机的东西,那么Val
将被推断为随机的类型,并且你可能会得到一个编译器错误.
如果我们能调用fn
的curried个版本,那就太好了,类型如下:
type CurriedFn = <
Namespace extends keyof ParamMap, Val extends Params<Namespace>
>(t: Namespace) => (u: Val) => string
然后调用curriedFn("a")
,它必然会返回一个类型为(u: Params<"a">) => string
的新函数,然后可以用Parameters<T>
utility type来探测该函数.
一个小问题是,您不能纯粹在类型级别上创建任意函数类型.这将要求Typescript 有higher kinded generic types个,就像microsoft/TypeScript#1213个中要求的那样.幸运的是,存在对这种类型操作of generic function values的支持.所以我们不是在Fn
号公路上采取行动,而是在fn
号公路上采取行动...或在打字员认为是类型Fn
的某个其他值上.
因此,我们需要一个函数来表示转换,如下所示:
const curry = <T, U, R>(
fn: (t: T, u: U) => R
) => (t: T) => (u: U) => fn(t, u);
然后,如果我们在fn
上调用它,就会得到我们想要的泛型类型:
const curriedFn = curry(null! as Fn);
// ^? const curriedFn: <
// Namespace extends keyof ParamMap, Val extends Params<Namespace>
// >(t: Namespace) => (u: Val) => string
万岁!
如果我们不能直接访问fn
的话该怎么做呢?在运行时,我们并不真正关心fn
,只要我们有一个类型为Fn
的值.我们可以用type assertion分假装这样做.
例如,这里断言了一个随机函数:
const curriedFn = curry((() => { }) as Function as Fn);
这很管用,但有点冗长.我们不需要实际的函数:
const curriedFn = curry(0 as any as Fn);
由于中间断言,这仍然是冗长的.假装我们有一个任意类型的值的最简单的方法可能是:
const curriedFn = curry(null! as Fn);
这是可行的,因为null!
使用the non-null assertion operator,它说的是"非null
的null
",不可能存在,所以它是never
类型的,可以断言为任何类型."null!
"有5个字,我想我没见过比这更短的了.
所有这些都会导致相同的类型.
现在我们可以用"a"
作为参数调用它,它会编译:
const takeKey = curriedFn("a");
// ^? const takeKey: (u: "a1" | "a2") => string
该函数的第一个参数是我们想要的类型:
type Key = Parameters<typeof takeKey>[0];
// ^? type Key = "a1" | "a2"
又一次万岁.
所以,这就对了.我们至少在某种程度上通过编程从Fn
中提取了"a1" | "a2"
.您也可以写curriedFn("b")
来查找"b1"| "b2"
.
但它也有明显的副作用.它发出以下JavaScript代码:
const curry = (fn) => (t) => (u) => fn(t, u);
const curriedFn = curry(null);
const takeKey = curriedFn("a");
这是毫无用处的,只有weird.对于某些用例来说,这可能不是一个大问题,但这并不理想.
Playground link to code个