6

我正在尝试使用类型保护来缩小复杂类型的范围。我希望类型保护的错误分支能够理解缩小类型的补充。

interface Model { side: 'left' | 'right'; }
interface LeftModel { side: 'left'; }
interface RightModel { side: 'right'; }
type Either = LeftModel | RightModel;

function isLeft(value: Either): value is LeftModel { // else is RightModel
  return value.side === 'left';
}

这似乎是不可能的,至少不是我打算这样做的方式。Typescript 很高兴推断 aEither可以是模型,但它不接受 aModel可以是Either. 这给出了一个错误:

declare const model: Model;
isLeft(model) // ts(2345)

这个问题是根本无法解决的吗?

如果不是,我如何让错误分支缩小到补码?

请参阅此Typescript Playground中的完整示例

编辑

在这个幼稚的例子中,ModelEither是等价的,但这可能不能一概而论。我可以通过组合两个类型保护来取得一些进展,以试图通知Model实际上是有效的类型系统Either(参见这个新的Playground)。然而,这给我留下了一个不需要的分支(见第 22 行),所以并不完全令人满意。

有没有办法让类型系统知道EitherModel严格等价?

我并不特别需要使用类型保护或联合类型,这是我第一次尝试解决问题,但也产生了自己的问题。只有当我们能够保证一个窄化类型的并集和它的相对补集确实等价于窄化类型时,联合类型才是可行的。这依赖于类型系统对补码的理解,但情况可能还不是这样。请参阅此打字稿补充搜索实用程序类型手册

有人建议使用fp-ts和/或monocle-ts来解决这个问题,但其中一些函数式编程概念仍然让我难以理解。如果有人知道如何在这里应用它们,那会很好。任何一个听起来都可以在这里提供帮助......

4

2 回答 2

3

联合运算符|不对联合中指定的类型的属性类型执行联合。

IE

type Either = LeftModel | RightModel === { side: 'left ' } | { side: 'right' } 
           !== { side: 'left' | 'right' } === Model

Either是严格意义上的并集,LeftModelRightModel不是类型的并集,side并且side

错误的摘录,我认为这说明了一切,

'Model' 类型的参数不能分配给 'Either' 类型的参数。类型“模型”不可分配给类型“RightModel”。属性'side'的类型是不兼容的。输入“左”| "right"' 不能分配给类型 '"right"'。类型 '"left"' 不能分配给类型 '"right"'。

于 2019-06-07T16:59:10.740 回答
2

您实际上可以使用类型联合而不使用类型保护来执行此操作:

interface Model { side: 'left' | 'right'; }
interface LeftModel extends Model { side: 'left'; }
interface RightModel extends Model { side: 'right'; }

function whatever(model: LeftModel | RightModel) {
  model.side // left or right

  if (model.side === 'left') {
    model.side; // left - model is also LeftModel at this point
  } else {
    model.side; // right - model is a RightModel
  }
}

操场

继承自Model实际上是可选的,因为它实际上是类型联合在这里完成工作。但它有助于将任何子类限制为仅“右”或“左”。

希望这是你想要完成的事情吗?

虽然类型保护不是必需的,但它仍然可以使用。它无法确定互补RightModel类型 - 但调用者可以,因为已经value约束到 and 的LeftModel联合RightModel

interface Model { side: 'left' | 'right'; }
interface LeftModel extends Model { side: 'left'; }
interface RightModel extends Model { side: 'right'; }

function isLeft(value: Model): value is LeftModel {
  return value.side === 'left';
}

function whatever(model: LeftModel | RightModel) { // This union makes the else branch work
  model.side // left or right

  if (isLeft(model)) {
    model.side; // left - model is also LeftModel at this point
  } else {
    model.side; // right - model is a RightModel
  }
}

带类型保护

于 2019-06-07T16:41:17.630 回答