使用流类型对函数的参数进行子类型化

Subtyping arguments of functions with flowtype

我的应用程序中几乎没有几种具有共同属性的类型。我将公共属性抽象为分离类型。现在我想编写一个函数,它接受任何具有共同基类型的类型。

这几行比以下文字更好地解释了问题:

type Box = {
  width: number,
  height: number,
}

// is like Box + it has position
type PositionedBox = Box & {
  type: 'positionedBox',
  left: number,
  top: number,
}

// is like Box + it has colo
type ColorBox = Box & {
  type: 'colorBox',
  color: string,
}

const logSize = (obj: Box) => {
  console.log({ w: obj.width, h: obj.height });
};

const logPosition = (obj: PositionedBox) => {
  console.log({ l: obj.left, t: obj.top });
};

const logColor = (obj: ColorBox) => {
  console.log({color: obj.color});
};

// this function should accept any Box like type
const logBox = (obj: Box) => {
  logBox(obj);
  // $ERROR - obj Box has no type property
  // (make sense at some point, but how to avoid it?)
  if (obj.type === 'colorBox') logColor(obj);
  if (obj.type === 'positionedBox') logPosition(obj);
}

问题是: logBox() 函数声明应该是什么样子才能通过流类型检查。

这些错误是合理的,因为没有什么可以阻止我将 {width: 5, height: 5, type: 'colorBox'} 传递给 logBox 函数,因为它是 Box 的子类型。如果你真的想接受 Box 的任何子类型,你将不得不处理后果,即检查 type 字段不会给你关于任何其他属性的信息。

如果您只想允许 Box 的特定子类型,那么您需要 disjoint union。下面我将 Box 重命名为 BaseBox 并添加了一个单独的 Box 类型,它是两个专用框的联合。这个例子通过了。

type BaseBox = {
  width: number,
  height: number,
}

// is like Box + it has position
type PositionedBox = BaseBox & {
  type: 'positionedBox',
  left: number,
  top: number,
}

// is like Box + it has colo
type ColorBox = BaseBox & {
  type: 'colorBox',
  color: string,
}

type Box = PositionedBox | ColorBox;

const logSize = (obj: Box) => {
  console.log({ w: obj.width, h: obj.height });
};

const logPosition = (obj: PositionedBox) => {
  console.log({ l: obj.left, t: obj.top });
};

const logColor = (obj: ColorBox) => {
  console.log({color: obj.color});
};

// this function should accept any Box like type
const logBox = (obj: Box) => {
  logBox(obj);
  // $ERROR - obj Box has no type property
  // (make sense at some point, but how to avoid it?)
  if (obj.type === 'colorBox') logColor(obj);
  if (obj.type === 'positionedBox') logPosition(obj);
}