Dear TypeScript, I know that your brother JavaScript sometimes may not want to see us together, but I think your qualities are worth it, that's why I love you!
Jokes apart, this is really a post that I have been desiring to write for a long time since I started to actually study the many features that TypeScript have and how wonderful it is. The code in the cover image is not just for show, in a simple example like that I can show many reasons why I'm in love with TypeScript and I want to use it as much as possible from now on. Therefore, without further ado, let's go write this love letter bit by bit.
When we talk about JavaScript we always think about flexibility. It confers a lot of flexibility in the way you write code, JavaScript is very permissive, you can add fields in objects, remove fields, functions can be passed as parameters, arrays can hold elements of any "type" (JavaScript has a very loose concept of type). However, all this flexibility has a cost. When I see a application with more than 100 lines and 1 file written in JavaScript it always looks a mess to me, no offense. I don't know from where things comes from and where they go. It is like stepping into a minefield. My background is from Java, where you have control of almost everything you code, way too much control sometimes.
That's where TypeScript comes to play, it mixes the flexibility of JavaScript, but putting it at bay with the control that static typed languages offer. For me, it have the best of two worlds: JavaScript and Java (never confuse them). So, let's go to the example. First, the Letter
interface.
export interface Letter {
from: string;
to: string;
message: string;
}
Okay, pretty normal, right? One thing that I like in interfaces in TypeScript is that you can have both properties and methods defined and the class that will implement them will need to have all these properties to satisfy it. It is different in Java, where "properties" are in the end constants inside an interface, they are not properties. Interfaces in TypeScript, however, show their power along with other classes. So, we present below the LoveLetter
class.
export class LoveLetter {
constructor(public from: string, public to: string) {}
get message(): string {
return `I love you, ${this.to}`;
}
}
Just by looking at the class you may not realize it if you don't know TypeScript wonderfulness, but this class completely satisfies the Letter
interface, 100%! First, the constructor. In Java it is always annoying to have to add parameters and assign them to the class properties right after, it is unnecessary boilerplate! I love TypeScript approach where by inserting public
, private
, etc. to the constructor's parameters you are declaring a class property and assigning the parameters' value to it all in one go. Therefore, LoveLetter
class has the string properties to
and from
that Letter
requires.
"But how about message
? In LoveLetter
it is a method, not a string property!", you might say. That's another very interesting feature of TypeScript: "getter methods". It can be used much like getters in Java, for example, but I think in TypeScript it has a gotcha that Java doesn't have. When you add the get
word before the function, TypeScript will treat it as if it were a property! So, we can do this: letter.message
instead of this: letter.message()
. Since it is considered a property to TypeScript it is a okay to satisfy the Letter
interface. It is very useful when you want to shape the data in another way, for example, for visual purposes, like I did with "I love you, #name#", using string interpolation to insert the name of recipient. It may not look a big difference in a simple example, but when you are trying to satisfy some big interfaces or having more complex use cases, it comes in handy.
For some who may be used to Java it may sound strange that the class implements the interface like that without any formal declaration like class Foo implements Bar
in Java, but it is one of the reasons why interfaces are so powerful in TypeScript, the class satisfies the interface merely by its shape, a formal declaration is not necessary. You can declare formally, however:
export class LoveLetter implements Letter {
constructor(public from: string, public to: string) {}
get message(): string {
return `I love you, ${this.to}`;
}
}
The declaration is exactly like Java and that way you will have error messages at the class level if the class is not satisfying the interface, but generally you will not have to be that explicit. So, if one class satisfies an interface only by shape, then you can go to the last part of our example:
const sendLetter = (letter: Letter) => {
console.log(
`From: ${letter.from}
To: ${letter.to}
${letter.message}
`
);
};
const loveLetter = new LoveLetter('Developer', 'TypeScript');
sendLetter(loveLetter);
Here we have a function that is assigned to a constant and is declared as an arrow function. In this case it doesn't have any special meaning, but arrow functions can make the code more elegant, principally when TypeScript/JavaScript are the home of callbacks. Also, it can solve some concerns around scopes, principally when it comes to this
, but we can talk about that in the future. Then we create a new LoveLetter
and assign it to a constant, nothing special, but the next line show some of the power of interfaces. Although the function receives a Letter
as parameter, since LoveLetter
satisfies the interface 100% then you are good to call the function with an instance of LoveLetter
as well! And what it is better is that you don't need to tell TypeScript anywhere that, it knows it automatically. Again, the interface is evaluated by its shape, if the shape is right, you are good to go!
It has been very interesting to learn more about TypeScript everyday and how it fuses the things that I like the most in the languages I already know in one beautiful language. There are many other features that are worth mentioning, but I think with this simple example you can get a sense of TypeScript greatness. I believe that TypeScript has a bright future in the market and I recommend it to everybody. Don't worry, I will not get jealous by the popularity of my dear TypeScript.
Top comments (4)
I love Typescript, It's great, but only if I'm working on Angular. With other platforms like react, node etc. It drives me so mad😂😂. Idk i might be the problem💁♂️
I think it is just a difference of experience, if you came from Javascript you may find annoying the constraints you have with TypeScript sometimes, but for me it is just natural and has the right amount of freedom coming from Java. I participated in a React Native course some months ago and the course app was written in Javascript, of course. After the end of the course I took the challenge of building it again in TypeScript. It certainly was not a very pleasant experience, but I don't blame TypeScript for that, there were various moments where I asked myself where on earth variable or property X came from because simply Javascript allows it to happen. I cannot blame TypeScript for doing its job. ¯_(ツ)_/¯
Right?! That's exactly the same way I feel hey.
Great article (and fun to read) !
The big advantage of TS for me is that you know the shape of data structures that you pass around from one place in your program to another place. With plain JS if I pass in an object as a parameter then to know the shape of that object I have to search for the caller or yet another place till I find out where the object is created.
Well of course if you don't use untyped '{}' objects but factory functions/constructors then you also have that sort of 'documentation' but TS really takes these things to the next level. I've studied it (and used it for real on one project) and I definitely like it. It's "deep" and very well thought out.