【问题标题】:Difference between extending and intersecting interfaces in TypeScript?TypeScript 中扩展接口和相交接口的区别?
【发布时间】:2021-03-16 20:40:30
【问题描述】:

假设定义了以下类型:

interface Shape {
  color: string;
}

现在,考虑以下方法为该类型添加额外的属性:

扩展

interface Square extends Shape {
  sideLength: number;
}

交叉口

type Square = Shape & {
  sideLength: number;
}

这两种方法有什么区别?

而且,为了完整性和出于好奇,还有其他方法可以产生可比较的结果吗?

【问题讨论】:

标签: typescript types intersection extends


【解决方案1】:

是的,有些差异可能与您的方案相关,也可能不相关。

也许最显着的差异在于具有相同属性键的成员在两种类型中的处理方式不同。

考虑:

interface NumberToStringConverter {
  convert: (value: number) => string;
}

interface BidirectionalStringNumberConverter extends NumberToStringConverter {
  convert: (value: string) => number;
}

上面的extends 会导致错误,因为派生接口声明的属性与派生接口中的键相同,但签名不兼容。

error TS2430: Interface 'BidirectionalStringNumberConverter' incorrectly extends interface 'NumberToStringConverter'.

  Types of property 'convert' are incompatible.
      Type '(value: string) => number' is not assignable to type '(value: number) => string'.
          Types of parameters 'value' and 'value' are incompatible.
              Type 'number' is not assignable to type 'string'.

但是,如果我们使用交集类型

type NumberToStringConverter = {
  convert: (value: number) => string;
}

type BidirectionalStringNumberConverter = NumberToStringConverter & {
  convert: (value: string) => number;
}

没有任何错误,进一步给出

// And this is a good thing indeed as a value conforming to the type is easily conceived
const converter: BidirectionalStringNumberConverter = {
    convert: (value: string | number) => {
        return (typeof value === 'string' ? Number(value) : String(value)) as string & number; // type assertion is an unfortunately necessary hack.
    }
}

const s: string = converter.convert(0); // `convert`'s call signature comes from `NumberToStringConverter`

const n: number = converter.convert('a'); // `convert`'s call signature comes from `BidirectionalStringNumberConverter`

Playground Link

这导致另一个有趣的区别,interface 声明是开放式的。可以在任何地方添加新成员,因为在同一声明空间中具有相同名称的多个 interface 声明被合并

这是合并行为的常见用法

lib.d.ts

interface Array<T> {
  // map, filter, etc.
}

array-flat-map-polyfill.ts

interface Array<T> {
  flatMap<R>(f: (x: T) => R[]): R[];
}

if (typeof Array.prototype.flatMap !== 'function') {
  Array.prototype.flatMap = function (f) { 
    // Implementation simplified for exposition. 
    return this.map(f).reduce((xs, ys) => [...xs, ...ys], []);
  }
}

请注意没有extends 子句存在,尽管在单独的文件中指定了接口都在全局范围内,并且按名称合并到具有两组成员的单个逻辑接口声明中。 (对于语法稍有不同的模块范围声明也可以这样做)

相比之下,存储在type 声明中的交集类型是封闭的,不能合并。

有很多很多不同。您可以在 TypeScript 手册中阅读有关这两种结构的更多信息。 InterfacesAdvanced Types 部分特别相关。

【讨论】:

  • 很好的答案。感谢您指出“覆盖”属性时的行为差异,对此一无所知。仅此一项就是在某些用例中使用类型的一个很好的理由。你能指出接口合并有用的情况吗?构建应用程序时是否有有效的用例(换句话说:不是库)?
  • Willem Aart 正如您所建议的那样,它对于编写库最有用,但是如果不是库的集合(包括您自己的应用程序),那么什么是应用程序。它对应用程序也非常有用。例如:interface Object {hasOwnProperty&lt;T, K extends string&gt;(this: T, key: K): this is {[P in K]?}} 通过引入额外的、更具体的签名将Object.prototype.hasOwnProperty 变成类型保护。 .
  • @AluanHaddad StringToNumberConverter 类型应该改名为BidirectionalStringNumberConverter,对吗?似乎其他实例可能已重命名...
  • @NathanChappell 感谢您的关注。不知道什么时候断的我已经更新了示例以使其可以编译,但它现在需要一个类型断言。我会对此进行更多研究。
  • @AluanHaddad 谢谢。 TS 似乎变化很快,因此可能无法跟上它(特别是因为他们似乎已经放弃维护规范......)
猜你喜欢
  • 2016-07-06
  • 2017-04-19
  • 1970-01-01
  • 2015-04-01
  • 2020-10-30
  • 2020-06-09
  • 2017-10-21
  • 2020-04-09
相关资源
最近更新 更多