如何在 TypeScript 中重用递归联合类型的公共部分?

Zby*_*byl 7 typescript typescript-generics

TLDR

这有效(游乐场):

type SimpleExpression = number | string | AddOperator<SimpleExpression> | PrintOperator<SimpleExpression>;
type ExtendedExpression = number | string | AddOperator<ExtendedExpression> | PrintOperator<ExtendedExpression> | PowerOperator<ExtendedExpression>;
Run Code Online (Sandbox Code Playgroud)

但是提取公共子类型不起作用(Playground):

type CommonExpression<E> = number | string | AddOperator<E> | PrintOperator<E>;
type SimpleExpression = CommonExpression<SimpleExpression>;
type ExtendedExpression = CommonExpression<ExtendedExpression> | PowerOperator<ExtendedExpression>;
Run Code Online (Sandbox Code Playgroud)

有什么办法可以解决吗?

详细描述

最近 TypeScript (3.7) 得到了对递归类型的扩展支持,但仍然不是一切皆有可能。
StackOverflow 上有很多解释递归类型的问题,但有一种有用的模式我找不到解决方案。

起点是一个表达式树,其中每个运算符都有作为表达式的子项。这有效:

type Expression = number | string | AddOperator | PrintOperator;
interface AddOperator {
  'first': Expression;
  'second': Expression;
}
interface PrintOperator {
  'value': Expression;
}
Run Code Online (Sandbox Code Playgroud)

现在我们想让它更通用,这样我们就有 SimpleExpression(只有addprint),以及也支持power运算符的ExtendedExpression 。我们可以这样做,并且它有效(Playground):

interface AddOperator<E> {
  'first': E;
  'second': E;
}
interface PrintOperator<E> {
  'value': E;
}
interface PowerOperator<E> {
  'value': E;
  'exp': E;
}

type SimpleExpression = number | string | AddOperator<SimpleExpression> | PrintOperator<SimpleExpression>;

type ExtendedExpression = number | string | AddOperator<ExtendedExpression> | PrintOperator<ExtendedExpression> | PowerOperator<ExtendedExpression>;
Run Code Online (Sandbox Code Playgroud)

但是现在如果我们想使用通用联合类型来分解公共部分,我们将失败(Playground):

type CommonExpression<E> = number | string | AddOperator<E> | PrintOperator<E>;
type SimpleExpression = CommonExpression<SimpleExpression>;
type ExtendedExpression = CommonExpression<ExtendedExpression> | PowerOperator<ExtendedExpression>;
Run Code Online (Sandbox Code Playgroud)

错误是:

Error: Type alias 'SimpleExpression' circularly references itself.
Error: Type alias 'ExtendedExpression' circularly references itself.
Run Code Online (Sandbox Code Playgroud)

所以问题是:有没有办法定义两种不同的表达式类型,它们共享共同的核心?

关于使用场景的一些背景知识:我们希望SimpleExpression在一个库中提供 ,但允许客户端代码定义额外的操作符,并为这些操作符注册处理程序。我们希望用户能够轻松定义他的ExtendedExpression类型,而无需过多键入。

T.D*_*art 0

当你使用

type SimpleExpression = number | string | AddOperator<SimpleExpression> | PrintOperator<SimpleExpression>;
type ExtendedExpression = number | string | AddOperator<ExtendedExpression> | PrintOperator<ExtendedExpression> | PowerOperator<ExtendedExpression>;
Run Code Online (Sandbox Code Playgroud)

至少有一条递归应该停止的路径。对于 的情况const simple: SimpleExpression = { value: { value: 5 } };,其类型为PrintOperator<PrintOperator<number>>


但如果你使用

type CommonExpression<E> = number | string | AddOperator<E> | PrintOperator<E>;
type SimpleExpression = CommonExpression<SimpleExpression>;
type ExtendedExpression = CommonExpression<ExtendedExpression> | PowerOperator<ExtendedExpression>;
Run Code Online (Sandbox Code Playgroud)

递归永远不能停止。事实上,递归本质上发生在类型声明本身中。SimpleExpression类型为CommonExpression<SimpleExpression>,但会扩展为CommonExpression<CommonExpression<SimpleExpression>>,然后CommonExpression<CommonExpression<CommonExpression<SimpleExpression>>>,依此类推,形成无限递归。这就是 TypeScript 不接受这种类型设置的原因。


然而有趣的是,

type SimpleExpression = AddOperator<SimpleExpression> | PrintOperator<SimpleExpression>;
Run Code Online (Sandbox Code Playgroud)

仍然被接受,但没有变量满足这种类型。