Don't be afraid of TypeScript
Your first types, interfaces, and errors
At some certain point in your JavaScript journey, you'll find yourself in the position where most hot jobs are looking for at least experience with TypeScript, most even asking you to have expertise with it. As someone who just got a feeling of control over JavaScript, it may be overwhelming to switch to types and hassles TypeScript can bring into your comfortable world. I have good and bad news for you. Bad news - you'll never have control over JavaScript π Good news - TypeScript is not that hard to start with and today I'll show you that π
Why do we need types?
It's no surprise, that most of you have seen memes like this one.
This is exactly why smart people have invented things like TypeScript - to help you write JavaScript and protect your applications. As you know JavaScript gives you slack on types and does the job for you, but people are getting used to the good very fast and start misusing it and this is where the issues come from. By design JavaScript is quite smart, it does type casting for you because it thinks you're wrong. If you know how type casting works, you are able to avoid most of those issues without much pain - somehow internet run for so many years already, right?
So, what is TypeScript?
First of all, TypeScript is a programming language that is built on top of JavaScript. It is a superset of JavaScript to be correct. I think not many of you know what superset actually is. I had a few drinks with Orta - a developer of a core TypeScript compiler team and also the one who wrote TypeScript documentation and he explained what superset is, so I want to share this with you.
The image below shows why TypeScript is a superset of JavaScript π
What it basically means is that TypeScript contains all the JavaScript parts and that TypeScript without any single type declared is a valid JavaScript - this is what superset is π
TypeScript allows us, developers, to create more robust and secure applications to provide a better experience for the end-user.
Okay, some boring stuff is over, now let's go straight to fun.
Your first line of TypeScript
Before we start with the first line, let's think of an interesting function that will show us the power of TypeScript. Look at the function below π
function printSumOfSomething(num1, num2) {
console.log(num1 + num2);
}
This is a very primitive function, but it has its traps π Let's say, that developer who wrote the function, was not as great as we are and the name of the function was not so clear. Let's say, it was something like this printCombined
- you may think it's funny, but it's actually very sad because sometimes you will see that kind of name in the production code. Calling this function we don't really know what to pass as arguments, so eventually, we may end up calling it with a string, right? Let's see different consequences of calling this function with different inputs π
function printCombined(num1, num2) {
console.log(num1 + num2, typeof (num1 + num2));
}
printCombined(2, 3); // returns 5
printCombined('a', 'b'); // returns 'ab'
printCombined(1, '3'); // returns '13'
printCombined(null, 5); // returns 5
printCombined(null, null); // returns 0 π
printCombined(undefined, '45'); // returns 'undefined45' πππ
Try it yourself
Create a file
index.ts
and copy our function in that file. Run the file withnode index.ts
and check the results. You have console.log that also will show you the type of the console.logged value. Try different combinations and see what kind of mess you can get. It's pretty fun. (Make sure you have typescript installed. If you don't want to install, you can use TypeScript playground byt this link TypeScript Playground
Now, since we have seen the messness of this function, let's add some TypeScript magic to it. First, I would like to introduce you to a great technique, that may help you in the future to structure your functions in a very clear way and understand them better. We will use a template to structure functions, so it will help to know what is expected from us. What is input and what is the output? No TypeScript yet π
Task: Write a function that takes two numbers and returns a sum of numbers.
Input: Two arguments as numbers
Input type: number, number
Desired output: Sum of two numbers
Return type: number
See, it gets clearer what we actually need to pass to the function and what we want our function to return. Oh, also we will change the name of the function to something meaningful. Now, let's finally get to some TypeScript stuff:
function printSumOfTwoNumbers(num1: number, num2: number): number {
console.log(num1 + num2, typeof (num1 + num2));
return num1 + num2;
}
As we said, we need to pass two numbers to the function and we need to return a sum of those as number. In TypeScript type declaration is made using :
after a variable, or argument in our case. The last :
after the brackets is the type of returning the value. I've added return
to a function because this is what our task is π
Now go to your code and replace the old function with this new one. Don't remove the examples from the previous part. You'll immediately see TypeScript complaining about some of our examples because what we pass is not what our function expects and this is a power of TypeScript. Providing strict typing enforces us to pass only values of those types.
Okay, we can remove the wrong examples and we will see, that now our function is secure. Also name is way more descriptive π
Let's get something harder.
Object Types and Interfaces
We won't go deep into the advanced TypeScript any time soon. My point is to show, that there's nothing scary about this language.
Usually, our functions are getting something more than just a few arguments. Usually, you'll end up with an array of objects to manipulate. This is where Object Types and Interfaces are handy.
Let's write another function that will take an array of objects as input, do something with it and return something. First, we will use our template to structure the function well.
Task: Write a function that takes a list of users, checks which user subscription is active, and returns only users with an active subscription.
Input: List (Array) of user objects
Input type:
id: string
name: string
isSubscriptionActive: boolean
Desired output: List (Array) of users with an active subscription
Return type:
id: string
name: string
isSubscriptionActive: boolean
Let's create a mock data and right a function π
const USERS = [
{
id: '01',
name: 'Sergii',
isSubscriptionActive: true,
},
{
id: '02',
name: 'Abhinav',
isSubscriptionActive: false,
},
{
id: '03',
name: 'Avonairik',
isSubscriptionActive: true,
},
{
id: '04',
name: 'Mystica',
isSubscriptionActive: true,
},
{
id: '05',
name: 'Majima',
isSubscriptionActive: false,
},
{
id: '06',
name: 'Ben',
isSubscriptionActive: true,
},
];
function getListOfUsersWithActiveSubscription(users) {
let usersWithActiveSubscriptions = users.filter((user) => user.isSubscriptionActive);
console.log(usersWithActiveSubscriptions);
return usersWithActiveSubscriptions;
}
getListOfUsersWithActiveSubscription(USERS);
We are passing an array of user objects, filtering by isSubscriptionActive
and returning result array, also console.logging to see results. As you can see, we don't use any types here, so let's try running this function with different inputs π
const BAD_USERS = [
{
id: '01',
name: 15,
isSubscriptionActive: true,
},
{
id: '02',
name: 'Abhinav',
isSubscriptionActive: 0,
},
{
id: '03',
name: 23,
isSubscriptionActive: true,
},
{
id: undefined,
name: 'Mystica',
isSubscriptionActive: 51,
},
{
id: '05',
name: 'Majima',
isSubscriptionActive: false,
},
{
id: null,
name: 'Ben',
isSubscriptionActive: 'wow',
},
];
getListOfUsersWithActiveSubscription(BAD_USERS);
// returns
/*
[
{ id: '01', name: 15, isSubscriptionActive: true },
{ id: '03', name: 23, isSubscriptionActive: true },
{ id: undefined, name: 'Mystica', isSubscriptionActive: 51 },
{ id: null, name: 'Ben', isSubscriptionActive: 'wow' }
]
*/
Well, yeah, you got it right, it returns whatever results, but not what we wanted to get. Is there a way to handle this with vanilla JavaScript? Of course, there is, let's look at the new function π
function getListOfUsersWithActiveSubscription(users) {
let usersWithActiveSubscriptions = users.filter((user) => {
if (typeof user.isSubscriptionActive === 'boolean') {
if (user.isSubscriptionActive) {
return user;
}
} else {
throw new Error('Provided type of user.isSubscriptionActive is not of type boolean');
}
});
console.log(usersWithActiveSubscriptions);
return usersWithActiveSubscriptions;
}
// returns
/*
[
{ id: '01', name: 'Sergii', isSubscriptionActive: true },
{ id: '03', name: 'Avonairik', isSubscriptionActive: true },
{ id: '04', name: 'Mystica', isSubscriptionActive: true },
{ id: '06', name: 'Ben', isSubscriptionActive: true }
]
*/
We must handle each case to check that typing is correct, so we can actually check if the type of isSubscriptionActive
is boolean and only if it is, do some operations with it. You can imagine how big this function grows in case we have to handle multiple object properties.
Try yourself
Go to your IDE or online TypeScript playground and insert this new function. Try it out with our
USERS
array andBAD_USERS
array.
Now, when we see how cumbersome it is to write this kind of function with vanilla JavaScript, let's add some TypeScript magic on top of it.
First, let's create a type description of our know object. We can use either Object Types or Interfaces, we will use any of them here.
// Dollar sign $ is optional, the name can be whatever you want
type $User = {
id: string;
name: string;
isSubscriptionActive: boolean;
};
// OR
interface $User {
id: string;
name: string;
isSubscriptionActive: boolean;
}
We won't go deep on the differences between these two in this article, we will dive deeper in the next article.
Now, we will add this type of definition to our constant USERS
.
const USERS: $User[] = [
{
id: '01',
name: 'Sergii',
isSubscriptionActive: true,
},
...
]
What we do here is declare the type of variable USERS
as $User[]
which means, making variable USERS be of Array of type $User. So, now, TypeScript knows that the variable must be an array and the contents of that array should be of a type we have described above.
Next, we will add some types to our function as well.
function getListOfUsersWithActiveSubscription(users: $User[]) {
let usersWithActiveSubscriptions = users.filter((user) => user.isSubscriptionActive);
console.log(usersWithActiveSubscriptions);
return usersWithActiveSubscriptions;
}
Right now the function knows that it should receive an argument of type $User[]
, it is being automatically passed down, because TypeScript knows that returning the value of array method filter
will be the same array of type $User
, so we don't need to bother writing those types again. That is called Type Inference, and we will look at it deeply in the next articles.
But right now, our function is incomplete. We need to declare what type of function should return to. This is extremely helpful for pure functions because thanks to Type Inference the variable we assign the return of this function will automatically get the correct Type.
function getListOfUsersWithActiveSubscription(users: $User[]): $User[] {
let usersWithActiveSubscriptions = users.filter((user) => user.isSubscriptionActive);
console.log(usersWithActiveSubscriptions);
return usersWithActiveSubscriptions;
}
So whenever we will assign the function result to any variable that variable will be $User[]
too π
For example:
const onlyActiveUsers = getListOfUsersWithActiveSubscription(USERS);
If you have been following with me, then you'll immediately see that TypeScript doesn't like our BAD_USERS
variable we passed to a function before, that's because BAD_USERS
is not of a type function is expecting to receive.
IntelliSense - more TypeScript magic
If you have been typing code yourself, you might have had the same issues I had doing that. It's quite boring to write all the code without some cool auto suggestions, right? Let me show you, how TypeScript creates suggestions for you.
Without TypeScript
With IntelliSense
You can clearly see the benefits of having IntelliSense and autocomplete. Another good part? When functions and objects grow, this is VERY helpful to prevent issues π
Recap
You see, TypeScript is not as scary as you might think. This is a truly great technology, that helps a lot to keep your code secured and structured well. It is a tool, that makes you a better developer because you have to think about in what format data comes and what format data should return. It does seem like it's actually taking more time, but in reality, it speeds up the process in the long term.
As I said, this is only an intro to TypeScript. I didn't want to bother you with various types and there's a great TypeScript Handbook for that. My point was to show, that you can start with TypeScript any time. What you can do is convert one of your projects to TypeScript as training. You can do it gradually and this was the whole point of this article. Next time we will go more in deep on how TypeScript works under the hood and how it makes this magic happen.
Stay around and thanks a lot for reading! As usual, you can catch me on Twitter @SergiiKirianov, and please leave your comments below. I would like to hear your feedback βΊοΈ