gpt4 book ai didi

typescript - 使用 TypeScript 类型系统创建一个流畅的、有状态的构建器

转载 作者:搜寻专家 更新时间:2023-10-30 21:44:32 24 4
gpt4 key购买 nike

我想创建一个可以表示 builder.a().b().build()builder.b().a().build() 的构建器,但不是 builder.a().build()builder.b().build()builder.a()。 a().build()

显然我可以在构建方法中进行验证,但我希望编译器对此进行提示(并且让 vs 代码提供自动完成功能)。我认为 TS 类型系统可以使用映射类型、并集和交集来表示这一点,但我不太明白。

有人知道我该怎么做吗?

最佳答案

让我们从最简单的部分开始,即实际实现。该类使用 this作为返回类型。稍后使用其他类型将发生真正的魔法:

class ConcreteBuilder {
a(): this {
return this;
}

b(): this {
return this;
}

build(): string {
return 'foo';
}
}

接下来我们需要一个泛型类型,我们可以在其中传入一个函数类型,它会将返回类型交换为其他类型。你马上就会明白为什么。我从 another answer 复制了这段代码:

type ArgumentTypes<T> = T extends (... args: infer U) => infer R ? U: never;
type ReplaceReturnType<O, N> = (...a: ArgumentTypes<O>) => N;

现在变得有趣了,这里是 Builder我想到的类型:

type Builder<K extends keyof ConcreteBuilder> = {
[U in K]: U extends 'build' ? ConcreteBuilder[U] : ReplaceReturnType<
ConcreteBuilder[U],
Builder<Exclude<K, U> extends never ? 'build' : Exclude<K, U>>
>
};

该类型有一个泛型参数 K仅限于作为 ConcreteBuilder 的键.这可能是 'a''a' | 'b' | 'build' .我们将使用此参数来确定哪些方法应该在结果类型中可用。

为此我们使用 mapped type遍历 K 中的所有键.对于每个 U in K , 除了 build方法我们修改方法的返回类型。

ConcreteBuilder[U]指方法的原始函数类型。

如果我们到达构建方法 (U extends 'build'),我们将保留来自 ConcreteBuilder 的原始类型.否则我们使用 ReplaceReturnType保留原始参数但将返回类型替换为:

Builder<Exclude<K, U> extends never ? 'build' : Exclude<K, U>>

很多东西要在这里打开。因此,正如您所见,我们将返回类型替换为 Builder .正如所讨论的那样,参数定义了哪些方法可用。因为这是方法的返回类型 U ,我们要删除 U从可用方法列表中,以防止调用它两次。这是使用 Exclude type 完成的.在我们的例子中,我们删除了 U来自键的联合 K .

因为这种递归类型一直持续到所有方法都从 K 中移除我们还需要一个基本案例。这就是conditional type这是为了。我们检查剩余的键 ( Exclude<K, U> ) 是否扩展 never这实质上意味着如果是“空的”。如果是这种情况,请使用 build 方法返回构建器.

最后,唯一剩下的就是 builder功能:

function builder(): Builder<Exclude<keyof ConcreteBuilder, 'build'>> {
return new ConcreteBuilder() as any;
}

它返回一个 Builder使用除 build 以外的所有方法.有一个 any强制转换,因为 ConcreteBuilder 的类型比 Builder 限制更少.


完整代码

type ArgumentTypes<T> = T extends (... args: infer U) => infer R ? U: never;
type ReplaceReturnType<O, N> = (...a: ArgumentTypes<O>) => N;

type Builder<K extends keyof ConcreteBuilder> = {
[U in K]: U extends 'build' ? ConcreteBuilder[U] : ReplaceReturnType<
ConcreteBuilder[U],
Builder<Exclude<K, U> extends never ? 'build' : Exclude<K, U>>
>
};

function builder(): Builder<Exclude<keyof ConcreteBuilder, 'build'>> {
return new ConcreteBuilder() as any;
}

class ConcreteBuilder {
a(): this {
return this;
}

b(): this {
return this;
}

build(): string {
return 'foo';
}
}

builder().a().b().build(); // ok
builder().b().a().build(); // ok
builder().build(); // error
builder().a().build(); // error
builder().b().build(); // error
builder().a().a().build(); // error
builder().b().b().build(); // error
builder().a().b().b().build(); // error
builder().a().a().b().build(); // error

Playground

关于typescript - 使用 TypeScript 类型系统创建一个流畅的、有状态的构建器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57828972/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com