I saw this pattern today in the WebAssembly package and was confused about this usage.
- Why is the interface name the same as the var name?
- Any difference between
c1
andc2
? new S({ a: 6 })
, How to understand theS
, 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 class
es, 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 var
s can.
But none of that applies to the example you've given.