我可以访问如下所示的外部库:

type ParamMap = {
  a: "a1" | "a2";
  b: "b1" | "b2";
};

type Params<T extends keyof ParamMap> = ParamMap[T];

export const fn = <Namespace extends keyof ParamMap, Val extends Params<Namespace>>(
  namespace: Namespace,
  key: Val
): string => `${namespace}.${key}`;

namespacea而只能访问fn时,有没有办法获得key的类型?我认为它应该看起来像:Parameters<fn>['a', infer T],但我无法让它工作.

推荐答案

您实际上不能直接这样做,因为您关心的类型是Val类型参数的constraint,并且目前没有办法将泛型约束infer.请参见microsoft/TypeScript#41040.

要做到这一点,我能想到的唯一方法是在typesexpressions中都经历了很多困难.这些表达式将被发送到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将被推断为随机的类型,并且你可能会得到一个编译器错误.

如果我们能调用fncurried个版本,那就太好了,类型如下:

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,它说的是"非nullnull",不可能存在,所以它是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

Typescript相关问答推荐

如何使用axios在前端显示错误体

将值添加到具有不同类型的对象的元素

输入元素是类型变体的对象数组的正确方法是什么?'

如何键入函数以只接受映射到其他一些特定类型的参数类型?

如何使用泛型自动推断TS中的类型

如何在Reaction路由v6.4加载器和操作中获得Auth0访问令牌?

如何在编译时为不带常量的参数引发错误

在数据加载后,如何使用NgFor在使用异步管道的可观测对象上生成子组件?

扩展参数必须具有元组类型或传递给REST参数

使用Redux Saga操作通道对操作进行排序不起作用

布局组件中的Angular 17命名路由出口

为什么我在这个重载函数中需要`as any`?

tailwind css未被应用-react

如何定义这样一个TypeScript泛型,遍历路由配置树并累积路径

react 路由不响应参数

在REACT查询中获取未定义的isLoading态

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

Typescript 从泛型推断类型

如何使用 runInInjectionContext 中的参数测试功能性路由防护

从函数参数推断函数返回类型