Reputation: 11415
In C# there's a quite huge difference between interfaces and classes. Indeed, a class represents a reference-type, so that we can actually create objects modeled on that class, while interfaces are meant to be contracts that a class sign to in order to ensure the existence of a certain behavior. In particular we can't create instances of interfaces.
The whole point with interfaces is to expose behavior. A class implements it by giving one explicit implementation of said behavior.
In that case, although interfaces may contain properties, most of the time we care about interfaces because of behavioral issues. So most of the type, interfaces are just contracts of behavior.
On TypeScript, on the other hand, I've seem something that made me quite uneasy, and in truth I've seen this more than once, which is the reason for this question.
In one tutorial I saw this:
export interface User {
name: string; // required with minimum 5 chracters
address?: {
street?: string; // required
postcode?: string;
}
}
But wait a minute. Why User
is an interface? If we think like C#, User
shouldn't be an interface. In truth, looking at it, it seems like we are defining the data type User
, instead of a contract of behavior.
Thinking like we do in C#, the natural thing would be this:
export class User {
public name: string;
public address: Address;
}
export class Address {
public street: string;
public postcode: string;
}
But this thing of using interfaces like we do with classes, to just define a data type, rather than defining a contract of behavior, seems very common in TypeScript.
So what interfaces are meant for in TypeScript? Why do people use interfaces in TypeScript like we use clases in C#? How interfaces should be properly used in TypeScript: to establish contracts of behavior, or to define properties and object should have?
Upvotes: 31
Views: 13722
Reputation: 4101
How interfaces should be properly used in TypeScript: to establish contracts of behavior, or to define properties and object should have?
Interfaces in TypeScript are shape contracts, describing the expected structure of an object. If a value has a certain interface annotation, you expect it to be an object featuring the members defined in the interface. Members can be values or functions (methods). Generally, their behavior (function bodies) is not part of the contract. But you can specify if they are readonly
or not.
So what interfaces are meant for in TypeScript? Why do people use interfaces in TypeScript like we use clases in C#?
Typescript interfaces can play the same role as C# interfaces if they are expected to be implemented by TypeScript classes.
But not only a class can implement an interface; any kind of value can:
interface HelloPrinter {
printHello(): void
}
The following object is not a class but nevertheless implements the interface:
{
printHello: () => console.log("hello")
}
Thus we can do
const o: HelloPrinter = {
printHello: () => console.log("hello")
}
and the TypeScript compiler won't complain.
The object implements our interface without forcing us to write a class.
Working with interfaces is more lightweight than working with (interfaces and) classes.
But if you need to know the type name (class/interface name) during runtime then classes are the right choice, because interface names are only known at compile time.
Upvotes: 3
Reputation: 9971
I also came to Typescript from a C# background and have wondered the same things. I was thinking along the lines of POCOs (is POTO a thing?)
So what interfaces are meant for in TypeScript?
The Typescript Handbook seems to say that interfaces are meant for "defining contracts within your code".
Why do people use interfaces in TypeScript like we use classes in C#?
I agree with @deceze's answer here.
John Papa expands on the subject of classes and interfaces on his blog. He suggests that classes are best suited for "creating multiple new instances, using inheritance, [and] singleton objects". So, based on the intent of Typescript interfaces as described in the Typescript Handbook and one man's opinion, it would appear that classes are not necessary to establish contracts in Typescript. Instead, you should use interfaces. (Your C# senses will still be offended.)
Interfaces should be properly used in TypeScript: to establish contracts of behavior, or to define properties and object should have?
If I understand the question, you are asking if interfaces should establish contracts of behavior or contracts of structure. To this, I would answer: both. Typescript interfaces can still be used the same way interfaces are used in C# or Java (i.e. to describe the behavior of a class), but they also offer the ability to describe the structure of data.
Furthermore, my coworker got on me for using classes instead of interfaces because interfaces produce no code in the compiler.
Example:
This Typescript:
class Car implements ICar {
foo: string;
bar(): void {
}
}
interface ICar {
foo: string;
bar(): void;
}
produces this Javascript:
var Car = (function () {
function Car() {
}
Car.prototype.bar = function () {
};
return Car;
}());
Upvotes: 17
Reputation: 10426
Interfaces in typescript are similar to interfaces in C# in that they both provide a contract. However opposed to C# interfaces which only contain methods typescript interfaces can also describe fields or properties that objects contain. Therefore they can also be used for things which are not directly possible with C# interfaces.
A major difference between interfaces and classes in typescript is that interfaces don't have a runtime representation and there won't be any code emitted for them. Interfaces are very broadly usable. For example you can use object literals to construct objects with satisfy an interface. Like:
let user: User = {
name: 'abc',
address: {
street: 'xyz',
},
};
Or you can assign any data objects (e.g. received through JSON parsing) to an interface (but your pre-checks should assert that it's really valid data). Therefore interfaces are very flexible for data.
On the other hand classes have a type associated at runtime to them and there is code generated. You can check the type at runtime with instanceof
and there's a prototype chain set up. If you define User
as a class it won't be a valid user unless you call the constructor function. And you can't just define any kind of suitable data to be a User
. You would need to create a new instance and copy the properties over.
My personal rule of thumb:
Upvotes: 11
Reputation: 20818
Using only the native deserialization mechanism, you cannot deserialize an instance of a specific class. You can only deserialize into a plain-old-javascript-object. Such objects can adhere to typescript interfaces but cannot be an instance of a class. If you need to deal with data that crosses a serialization boundary such as data expected from a webservice, use interfaces. If you need to generate new instances of such values yourself, just construct them literally or create a convenience function that returns them - objects that adhere to that interface.
A class can itself implement an interface, but it might get confusing if you expect to deal with both locally constructed class instances AND deserialized, reconstituted plain objects. You'd never be able to rely on the class-basis of the object and so there'd be no benefit of also defining it as a class for that exact purpose.
I've had success in creating a ServerProxy module responsible for sending code back and forth from a webservice - the webservice call and the returned result. If you're binding to knockout models or similar, you can have a class that encapsulates the ui-bound model with a constructor that knows how to lift a returned plain-old-javascript-object that adheres to the webservice's interface-only contract into an instance of your model class.
Upvotes: 1
Reputation: 522510
Consider that in Javascript, data is often exchanged as plain objects, often through JSON:
let data = JSON.parse(someString);
Let's say this data
is an array of User
objects, and we'll pass it to a function:
data.forEach(user => foo(user))
foo
would be typed like this:
function foo(user: User) { ... }
But wait, at no point did we do new User
! Should we? Should we have to write a class User
and map
all the data
to it, even though the result would be exactly the same, an Object
with properties? No, that would be madness just for the sake of satisfying the type system, but not change anything about the runtime. A simple interface
which describes how the specific object is expected to look like (to "behave") is perfectly sufficient here.
Upvotes: 27