TypeScript中扩展和交叉接口之间的区别?

Wil*_*art 13 extends types intersection typescript

假设定义了以下类型:

interface Shape {
  color: string;
}
Run Code Online (Sandbox Code Playgroud)

现在,请考虑以下方法为此类型添加其他属性:

延期

interface Square extends Shape {
  sideLength: number;
}
Run Code Online (Sandbox Code Playgroud)

路口

type Square = Shape & {
  sideLength: number;
}
Run Code Online (Sandbox Code Playgroud)

两种方法有什么区别?

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

Alu*_*dad 16

是的,您的方案中可能存在或可能存在相关的差异.

也许最重要的是在两种类型中存在具有相同属性键的成员的处理方式的差异.

考虑:

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

interface BidirectionalStringNumberConverter extends NumberToStringConverter {
  convert: (value: string) => number;
}
Run Code Online (Sandbox Code Playgroud)

extends一个错误上述结果,因为derriving接口声明具有相同的密钥作为在派生接口但一项所述的带签名不兼容的特性.

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'.
Run Code Online (Sandbox Code Playgroud)

但是,如果我们采用交叉类型

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

type BidirectionalStringNumberConverter = NumberToStringConverter & {
    convert: (value: string) => number;
}
Run Code Online (Sandbox Code Playgroud)

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

declare const converter: BidirectionalStringNumberConverter;

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

converter.convert('a'); // `convert`'s call signature comes from `BidirectionalStringNumberConverter`

// And this is a good thing indeed as a value conforming to the type is easily conceived

const converter: BidirectionalStringNumberConverter = {
  convert: (value: string | number) =>
    typeof value === 'string'
      ? Number(value)
      : String(value)
}
Run Code Online (Sandbox Code Playgroud)

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

这是合并行为的常见用法

lib.d.ts

interface Array<T> {
    // map, filter, etc.
}
Run Code Online (Sandbox Code Playgroud)

阵列平面地图polyfill.ts

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

if (typeof Array.prototype.flatMap !== 'function') {
    Array.prototype.flatMap = function (f) {
        return this.map(f).reduce((xs, ys) => [...xs, ...ys], []);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

相比之下,存储在type声明中的交集类型是封闭的,不受合并的影响.

有很多很多不同之处.您可以在TypeScript手册中阅读有关这两种结构的更多信息.在接口先进的各类节特别相关.

  • 很好的答案。感谢您指出“覆盖”属性时的行为差异,但不知道这一点。这本身就是在某些用例中使用类型的一个很好的理由。你能指出界面合并有用的情况吗?构建应用程序时是否有有效的用例(换句话说:不是库)? (2认同)