gpt4 book ai didi

typescript - 当某些具有相同的形状/属性时从联合类型中提取

转载 作者:行者123 更新时间:2023-12-04 04:08:27 26 4
gpt4 key购买 nike

我正在尝试构建 CQRS 样式命令总线的实现,其中命令总线的接口(interface)是单个函数,dispatch:

const result = dispatch(message)

dispatch 函数的类型签名是这样的:

type Dispatch<Message, Result> = (message: Message) => Result

例如,想象一下,我们想要为 Git 存储库提供一个接口(interface)。一些消息可能是:

class Clone {
constructor(public readonly remoteUrl: string) { }
}

class Checkout {
constructor(public readonly branchName: string) { }
}

class RevParse {
constructor(public readonly branchName: string) { }
}

对于每个Message,都有一个已知类型的Result。经过一些实验,我认为表达“协议(protocol)”的正确方式是这样的:

type Protocol = [Clone, void] | [Checkout, void] | [RevParse, string]

Protocol 是元组类型的联合,每个元组类型都表示Message 和预期类型的​​Result 之间的关系。在这个例子中,只有 RevParse 应该返回任何有趣的东西——其他的只是返回 void。

为了能够计算出给定 Message 的预期 Result,我有 learned我可以使用 Extract 实用程序类型,如下所示:

type Result<Message> = Extract<Protocol, [Message, any]>[1]
type Dispatch<Message extends Protocol[0]> = (message: Message) => Result<Message>

但是,我发现当两个消息具有相同的属性时,这似乎会失败。例如,我可以从 Checkout 消息中返回一个 string。我假设这是因为 Extract 在给定 Checkout 类型以查找正确的 Result,因为这两种类型看起来都像 { branchName: string}

// should fail with type error because the protocol says Checkout should return void.
const checkoutResult: Result<Checkout> = 'string'
// const checkoutResult: string | void

我对这个问题还有其他疑问,但首先我需要了解表达 MessageResult 类型之间关系的正确方法。我对 Result 查找的假设是否正确?我应该做一些与使用元组联合完全不同的事情吗?我是否需要为每条消息添加一些属性来唯一标识它?还有别的吗?

Playground Link

最佳答案

我真的不知道您要构建的用例和上下文,但我觉得 dispatch返回不同的类型在这里不是一个好的做法(在某些情况下可能是必要的:https://softwareengineering.stackexchange.com/questions/225682/is-it-a-bad-idea-to-return-different-data-types-from-a-single-function-in-a-dyna)。你可以使用 Strategy Pattern .

无论如何,假设这个用例是合法的:

正如您所提到的,Extract匹配 CheckoutRevParse什么时候Checkout被赋予结果。事实上,Typescript doc说:

Extract<T,U> Constructs a type by extracting from T all properties that are assignable to U

在你的例子中,[Checkout, void][RevParse, string]可分配给 [Checkout, any] (当你做结果时)这意味着 CheckoutRevParse可分配给 Checkout , 和 void & string可分配给 any .

原因是对于类,Typescript 使用结构类型如下,根据 documentation :

they (classes) have both a static and an instance type. When comparing two objects of a class type, only members of the instance are compared. Static members and constructors do not affect compatibility.

相反

Private and protected members in a class affect their compatibility.

因此,重要的是类型的结构,而不是类型的名称。如果两种类型在结构上是等价的,那么它们是可以互换的。如果您不希望这种情况发生,您可以使用“名义打字”。有几种方法,尽管我认为它应该被特别使用,因为它现在还不是 Typescript 的原生方法。有电流 PR所以它可能很快就会在 TS 中成为原生的,使用“unique”关键字。现在:

  1. 您可以将私有(private)属性添加到您的类中以使其与众不同,即使它们具有不同的名称也是如此。

class Clone {
private __nominal: void;
constructor(public readonly remoteUrl: string) { }
}

class Checkout {
private __nominal: void;
constructor(public readonly branchName: string) { }
}

class RevParse {
private __nominal: void;
constructor(public readonly branchName: string) { }
}
  1. 您可以使用不同名称的静态属性,使用“品牌”后缀,正如Typescript team 所建议和使用的那样

class Clone {
_cloneBrand: any;
constructor(public readonly remoteUrl: string) { }
}

class Checkout {
_checkoutBrand: any;
constructor(public readonly branchName: string) { }
}

class RevParse {
_revParseBrand: any;
constructor(public readonly branchName: string) { }
}

这将解决您的第二个问题,它将变成:

// const checkoutResult: void

对于调度功能,您应该使用已经定义的结果类型执行以下操作:

 const dispatch = <Message extends Protocol[0]>(message: Message): Result<Message> => {
if (message instanceof Clone) {
// do clone stuff
return
}
if (message instanceof Checkout) {
// do checkout stuff

// should insist that I return void here
return 'should not be allowed'
}
if (message instanceof RevParse) {
const { branchName } = message
// do revparse stuff
return 'abcdef1234'
}
throw new Error(`What is this? ${message}`)
}

这将解决您的第一个问题, typescript 现在将考虑返回 dispatch(new Clone('url'))作为无效

关于typescript - 当某些具有相同的形状/属性时从联合类型中提取,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62151019/

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