Difference between extending and intersecting interfaces in TypeScript?

旧城冷巷雨未停 提交于 2020-01-10 12:11:08

问题


Let's say the following type is defined:

interface Shape {
  color: string;
}

Now, consider the following ways to add additional properties to this type:

Extension

interface Square extends Shape {
  sideLength: number;
}

Intersection

type Square = Shape & {
  sideLength: number;
}

What is the difference between both approaches?

And, for sake of completeness and out of curiosity, are there other ways to yield comparable results?


回答1:


Yes there are differences which may or may not be relevant in your scenario.

Perhaps the most significant is the difference in how members with the same property key are handled when present in both types.

Consider:

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

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

The extends above results in an error because the derriving interface declares a property with the same key as one in the derived interface but with an incompatible signature.

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'.

However, if we employ intersection types

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

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

There is no error whatsoever and further given

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

This leads to another interesting difference, interface declarations are open ended. New members can be added anywhere because multiple interface declarations with same name in the same declaration space are merged.

Here is a common use for merging behavior

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) {
        return this.map(f).reduce((xs, ys) => [...xs, ...ys], []);
    }
}

Notice how no extends clause is present, although specified in separate files the interfaces are both in the global scope and are merged by name into a single logical interface declaration that has both sets of members. (the same can be done for module scoped declarations with slightly different syntax)

By contrast, intersection types, as stored in a type declaration, are closed, not subject to merging.

There are many, many differences. You can read more about both constructs in the TypeScript Handbook. The Interfaces and Advanced Types section are particularly relevant.



来源:https://stackoverflow.com/questions/52681316/difference-between-extending-and-intersecting-interfaces-in-typescript

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!