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

问题描述 投票:0回答:2

假设定义了以下类型:

interface Shape {
  color: string;
}

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

扩展

interface Square extends Shape {
  sideLength: number;
}

十字路口

type Square = Shape & {
  sideLength: number;
}

这两种方法有什么区别?

并且,为了完整起见并出于好奇,是否还有其他方法可以产生可比较的结果?

typescript types intersection extends
2个回答
135
投票

是的,存在一些差异,这些差异可能与您的场景相关,也可能不相关。

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

考虑:

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;
}

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

这确实是一件好事,因为很容易想到符合类型的值

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`

游乐场链接

这导致了另一个有趣的区别,

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

这是合并行为的常见用途

lib.d.ts

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

数组-平面-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 手册中阅读有关这两种构造的更多信息。 对象类型从类型创建类型部分特别相关。


1
投票

Typescript 已更新其文档,其中包含描述此场景的部分!

https://www.typescriptlang.org/docs/handbook/2/objects.html#interfaces-vs-intersections

“两者之间的主要区别在于如何处理冲突,这种区别通常是您在接口和交集类型的类型别名之间选择一个的主要原因之一。”

© www.soinside.com 2019 - 2024. All rights reserved.