请考虑以下伪代码尝试使用函数类型参数定义高阶类型函数M<?>
:
type HigherOrderTypeFn<T, M<?>> = T extends (...)
? M<T>
: never;
M<?>
在语法上是不正确的 TypeScript,但将类型签名声明为会在第二行HigherOrderTypeFn<T, M>
产生错误。Type 'M' is not generic. ts(2315)
假设这种类型目前在 TS 中无法表示,我是否正确?
请考虑以下伪代码尝试使用函数类型参数定义高阶类型函数M<?>
:
type HigherOrderTypeFn<T, M<?>> = T extends (...)
? M<T>
: never;
M<?>
在语法上是不正确的 TypeScript,但将类型签名声明为会在第二行HigherOrderTypeFn<T, M>
产生错误。Type 'M' is not generic. ts(2315)
假设这种类型目前在 TS 中无法表示,我是否正确?
你是对的,它目前无法在 TypeScript 中表示。有一个长期开放的 GitHub 功能请求microsoft/TypeScript#1213,其标题可能应该类似于“支持更高种类的类型”,但目前的标题是“允许类在其他参数类中参数化”。
讨论中有一些关于如何在当前语言中模拟这种更高种类的类型的想法(请参阅此评论以获取具体示例),但在我看来,它们可能不属于生产代码。如果您有一些要实施的特定结构,也许可以建议一些适当的东西。
但是在任何情况下,如果您想增加这种情况发生的机会(可能可以忽略不计,不幸的是),您可能想要解决该问题并给出一个和/或描述您的用例,如果您认为它特别引人注目已经有什么了。好的,希望有帮助;祝你好运!
对于您和其他寻找解决方法的人,您可以尝试基于占位符的简单想法(请参阅jcalz 提到的讨论中的此评论):
type Placeholder = {'aUniqueKey': unknown};
type Replace<T, X, Y> = {
[k in keyof T]: T[k] extends X ? Y : T[k];
};
因此,您的函数如下所示:
type HigherOrderTypeFn<T, M> = T extends (...) ? Replace<M, Placeholder, T> : never;
并被称为例如:
type M<U> = U[];
type X = HigherOrderTypeFn<number, M<Placeholder>> // is number[] (if ... is number)
对于遇到这种情况的人,TypeScript discord 服务器上有一个很好的例子:
export interface Hkt<I = unknown, O = unknown> {
[Hkt.isHkt]: never,
[Hkt.input]: I,
[Hkt.output]: O,
}
export declare namespace Hkt {
const isHkt: unique symbol
const input: unique symbol
const output: unique symbol
type Input<T extends Hkt<any, any>> =
T[typeof Hkt.input]
type Output<T extends Hkt<any, any>, I extends Input<T>> =
(T & { [input]: I })[typeof output]
interface Compose<O, A extends Hkt<any, O>, B extends Hkt<any, Input<A>>> extends Hkt<Input<B>, O>{
[output]: Output<A, Output<B, Input<this>>>,
}
interface Constant<T, I = unknown> extends Hkt<I, T> {}
}
可以如下使用。下面的代码片段定义了 a SetFactory
,您可以在其中指定创建工厂时所需的设置类型,例如typeof FooSet
或typeof BarSet
。typeof FooSet
是 a 的构造函数,FooSet
就像一个更高种类的类型,构造函数类型接受 anyT
并返回 a FooSet<T>
。包含几个方法,SetFactory
例如createNumberSet
,它返回给定类型的新集合,类型参数设置为number
。
interface FooSetHkt extends Hkt<unknown, FooSet<any>> {
[Hkt.output]: FooSet<Hkt.Input<this>>
}
class FooSet<T> extends Set<T> {
foo() {}
static hkt: FooSetHkt;
}
interface BarSetHkt extends Hkt<unknown, BarSet<any>> {
[Hkt.output]: BarSet<Hkt.Input<this>>;
}
class BarSet<T> extends Set<T> {
bar() {}
static hkt: BarSetHkt;
}
class SetFactory<Cons extends {
new <T>(): Hkt.Output<Cons["hkt"], T>;
hkt: Hkt<unknown, Set<any>>;
}> {
constructor(private Ctr: Cons) {}
createNumberSet() { return new this.Ctr<number>(); }
createStringSet() { return new this.Ctr<string>(); }
}
// SetFactory<typeof FooSet>
const fooFactory = new SetFactory(FooSet);
// SetFactory<typeof BarSet>
const barFactory = new SetFactory(BarSet);
// FooSet<number>
fooFactory.createNumberSet();
// FooSet<string>
fooFactory.createStringSet();
// BarSet<number>
barFactory.createNumberSet();
// BarSet<string>
barFactory.createStringSet();
简要说明这是如何工作的(以FooSet
并number
作为示例):
Hkt.Output<Const["hkt"], T>
. 用我们的示例类型替换,这变成了Hkt.Output<(typeof FooSet)["hkt"], number>
. 现在的魔法涉及把它变成一个FooSet<number>
(typeof FooSet)["hkt"]
决定FooSetHkt
. 通过将有关如何创建 a 的信息FooSet
存储hkt
在FooSet
. 您需要为每个受支持的类执行此操作。Hkt.Output<FooSetHkt, number>
. 解析Hkt.Output
类型别名,我们得到(FooSetHkt & { [Hkt.input]: number })[typeof Hkt.output]
. 用于创建唯一属性的唯一符号Hkt.input
/Hkt.output
帮助,但我们也可以使用唯一字符串常量。Hkt.output
. FooSetHkt
这对于每个类都不同,并且包含有关如何使用类型参数构造具体类型的详细信息。FooSetHkt
将输出属性定义为类型FooSet<Hkt.Input<this>>
。Hkt.Input<this>
只需访问 的Hkt.input
属性FooSetHkt
。它会解析为unknown
,但通过使用交集 FooSetHkt & { [Hkt.input]: number }
,我们可以将Hkt.input
属性更改为number
。因此,如果我们已经达到了我们的目标,Hkt.Input<this>
请决心number
并FooSet<Hkt.Input<this>>
决心FooSet<number>
。对于问题中的示例,Hkt.Output
本质上是所要求的,只是类型参数颠倒了:
interface List<T> {}
interface ListHkt extends Hkt<unknown, List<any>> {
[Hkt.output]: List<Hkt.Input<this>>
}
type HigherOrderTypeFn<T, M extends Hkt> = Hkt.Output<M, T>;
// Gives you List<number>
type X = HigherOrderTypeFn<number, ListHkt>;