• Difference between extending and intersecting interfaces in TypeScript?


    Asked 3 years, 7 months ago
    Modified 10 months ago
    Viewed 18k times
     
    88

    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 Answer

    98
     

    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

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

    There is no error whatsoever and further given

    // 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

    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) { 
        // Implementation simplified for exposition. 
        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.

    • 2
      Great answer. Thanks for pointing out the difference in behaviour when 'overriding' properties, didn't know about that. That alone is a good reason to use types in certain use cases. Can you point out situations where interface merging is useful? Are there valid use cases when building applications (in other words: not libraries)?   Oct 6, 2018 at 18:42 
    •  
      Willem Aart as you suggest, it is most useful for writing libraries, but what is an application if not a collection of libraries (including your own app). It can be extremely useful for applications as well. Ex: interface Object {hasOwnProperty<T, K extends string>(this: T, key: K): this is {[P in K]?}} which turns Object.prototype.hasOwnProperty into a type guard by introducing an additional, more specific signature for it. .   Oct 6, 2018 at 19:12 
    • 1
      @AluanHaddad the StringToNumberConverter type should be instead named BidirectionalStringNumberConverter, correct? It seems like the other instances were possibly renamed...   Jul 4, 2019 at 19:56
    • 1
      @NathanChappell thank you for catching that. I don't know when that broke. I've updated the example to make it compile, but it now requires a type assertion. I will look into this more.   Sep 22, 2020 at 8:37
    • 1
      @AluanHaddad thanks. TS seems to be changing quite fast, so it's probably impossible to keep up with it (especially since they seem to have abandoned maintaining a specification...)   Sep 22, 2020 at 8:47
  • 相关阅读:
    Eclipse扩展安装插件方式
    Tomcat通过JNDI方式链接MySql数据库
    ArrayList实现根据某属性大小相间排序
    JMeter 怎么保存登录状态
    JMeter怎么使用代理服务器
    JMeter模拟多个用户进行登录
    JMeter怎么在get URL请求、POST请求中添加动态参数用于服务器段安全验证
    (转)sizeof()和_countof()区别
    潜心修炼, 生活,会给你一个惊喜~
    最初的梦想
  • 原文地址:https://www.cnblogs.com/sexintercourse/p/16318398.html
Copyright © 2020-2023  润新知