Home > Enterprise >  why the `interface` name the same as the `var`'s?
why the `interface` name the same as the `var`'s?

Time:10-12

I saw this pattern today in the WebAssembly package and was confused about this usage.

  1. Why is the interface name the same as the var name?
  2. Any difference between c1 and c2?
  3. new S({ a: 6 }), How to understand the S, is it an interface or the var?
interface S {
  a: number,
}

declare var S: {
   b: number,
   new (value?: any): S;
}

const c1 = { a: 6 };
const c2 = new S({ a: 6 });

CodePudding user response:

Values and types don't share a namespace in TypeScript, so in general there's no necessary relationship between a named value (like var S) and a named type (like interface S); they just happen to share a name.

However, when you write a class declaration, it brings into existence both a named value corresponding to the class constructor, and a named type corresponding to instances of the class. This allows things like const d: Date = new Date(), where the first Date is the instance type, and where the second Date is the class constructor value. (See this answer to another question for a long explanation of this)

So, if I were to happen to write this:

class S {
  a: number;
  static b: number = 1;
  constructor(value?: any) {
    this.a = (typeof value === "number") ? value : 123;
  }
}

It would create a value named S that's the class constructor, and a type named S which is an instance of that class. The class constructor can be called via new S(value) where value is an optional parameter of any type, and it produces a value of type S. A value of type S has an a property of type number. Also note that the class constructor S has a static property named b of type number.

If you ask "is S a variable or a type" without more context, the answer is "it's both" or "it depends".

Anyway you can test its behavior like this:

console.log(S.b) // 1
const c1: S = { a: 6 };
console.log(c1.a) // 6
console.log(c1 instanceof S) // false
const c2: S = new S({ a: 6 });
console.log(c2.a) // 123
console.log(c2 instanceof S) // true

Note that c1 is just some object with an a property, and is not necessarily an instance of the S class, even though it conforms to the S interface. On the other hand, c2 is definitely an instance of the S class, although its a property isn't necessarily related to the value of the a property passed into the constructor (after all that value argument can be any value, and the particular constructor implementation doesn't know that value has an a property and doesn't try to look for one).


Now, if I were to write a declaration file for that class, I'd write this:

declare class S {
    a: number;
    static b: number;
    constructor(value?: any);
}

But another way to write this is to declare the S constructor as a variable of the appropriate type, and to declare an interface named S of the appropriate shape:

interface S {
  a: number,
}

declare var S: {
  b: number,
  new(value?: any): S;
}

That behaves essentially the same. So you can think of the var/interface declaration pair above as being a "synonym" for the class declaration.


Why would someone write it this way? Well, that's not completely obvious. According to microsoft/TypeScript#9154, older versions of TypeScript didn't let you merge declarations into the instance side of classes, so one would often leave it as an interface to allow this. Nowadays that's not a concern anymore, really.

In general, the var/interface pair is more flexible, since there are still things you can do with interface merging that you can't do with class merging. For example, you can give the var a type of another interface (say declare var S: SConstructor; interface SConstructor { /*...*/ }), and allow people to merge new members into the constructor later. And class constructors can't be marked as callable without new, while vars can.

But none of that applies to the example you've given.

Playground link to code

  • Related