typescriptoopinheritancemultiple-inheritanceprototypal-inheritance

Why does TS allow interface multiple inheritance?


I'm learning TypeScript about extending the interface. I unintentionally recognized that TypeScript allows extending interface from multiple classes. That made me surprised and I have researched a lot to find more information about but until now, I have not been able to answer questions.

I am wondering that: although TypeScript prevents class inheriting from multi-classes (class has 2 or more direct base classes), why does it permit one interface to extend directly from multiple classes like that code below:


    class Student{
        name:string = ''
    }
    
    class Employee {
        salary:number =0
    }
    
    interface Work_Study extends Student,Employee{
        description:string;
    }
    

Solution

  • When you write a class declaration in TypeScript like

    class Student {
        name: string = ''
    }
    

    it brings into scopes two things named Student. One is the class constructor value that exists at runtime in JavaScript. It's an object named Student and you can write new Student() to construct a new class instance value. The other is the class instance type that exists only in TypeScript's type system. It's a type named Student and you can write let student: Student to tell TypeScript that the type of student is the instance type of the Student class. Even though they share a name and are related, they are not the same thing.

    TypeScript doesn't get confused about which thing you're referring to because values and types live in different syntactic contexts in TypeScript. If you write x = new X(), that X is definitely a value, not a type. If you write let x: X;, that X is definitely a type, not a value. If you write interface Z extends X {}, that X is definitely a type, not a value.

    See the TypeScript handbook documentation for more details.


    It's also important to note that the class instance type behaves exactly like an interface. It's not declared using the interface keyword, but it behaves the same. Indeed, the Student declaration above is quite similar to the pair

    interface Student {
      name: string
    }
    
    const Student: new () => Student = class {
      name: string = ""
    };
    

    where the two things named Student are defined explicitly and separately.


    If we put those together, we can understand what

    class Student {
      name: string = ''
    }
    
    class Employee {
      salary: number = 0
    }
    
    interface Work_Study extends Student, Employee {
      description: string;
    }
    

    is doing. The interface declaration with the extends clause is just declaring a new interface which inherits from the Student and Employee types. It has nothing whatsoever to do with the Student and Employee constructors. It's exactly the same as

    interface Student {
      name: string
    }
    
    interface Employee {
      salary: number;
    }
    
    interface Work_Study extends Student, Employee {
      description: string;
    }
    

    And multiple inheritance of interfaces is unproblematic.


    On the other hand, something like

    class Work_Study extends Student, Employee {
      description: string = "oops"
    }
    

    is different, in that it's a class declaration and involves the value space. It's invalid because that's the JavaScript extends clause which only allows extending a single parent class. Multiple inheritance in JavaScript classes is prohibited (to avoid the diamond problem where it's ambiguous which implementation to use).

    While class Work_Study extends Student, Employee {} and interface Work_Study extends Student, Employee {} look similar, they are in completely different syntactic contexts, and behave differently. The former is illegal multiple class inheritance, while the latter is legal multiple interface inheritance.

    Playground link to code