Learn TypeScript #5, Basic Classes

Transcript:

Hello, everybody! Today I’m going to show you how to use classes in TypeScript. There’s a lot to cover about classes, so for this video we’re going to stick to the basics, and then in future videos we’ll dig into the really advanced stuff.

At their core, classes are a way of describing what data you want certain objects to hold, along with methods that act on that data in various ways. There are a lot of other features built on top of this, but that is the core.

If you come from a language like C# or Java, classes in TypeScript are very similar to what you worked with there. Though there are some important differences in the low-level details, which we’ll be covering in later videos.

Unlike Java or C#, however, there’s nothing in TypeScript that is forcing you to use classes. So if you prefer to write your code in some other way, you are perfectly free to do so. Classes are just another tool you have in your toolbelt as a TypeScript developer.

If you’ve been following the development of JavaScript over the years, you’ll know that the latest JavaScript standards have added support for classes. TypeScript classes build directly on these standards, but with the added benefit that you can use TypeScript to target older versions of JavaScript, and the classes you write will work perfectly well when compiled down to code that’s compatible with older browsers that don’t support classes in JavaScript directly.

So let’s start off with a simple example class.

Let’s say we’re making game and we’re planning on having multiple players and a decent amount of logic common to the players to handle things like movement, displaying the players, and so on. This sounds like a good candidate for a class, so let’s create a class called Player.

Creating a class is very simple, we’ll write the class keyword followed by our class name, and then the contents of the class are contained within curly brackets. If you saw the previous video on interfaces, you’ll notice that class definitions and interface definitions share a lot of parallels, which you’ll see more of going forward.

Let’s pause for a moment and note that this is actually a complete class. It’s empty, so it’s not very useful, but before we start putting things in it, I want to show you how to create an instance of this class.

We’ll create a new variable called “p”, and set it equal to “new Player()”.

The “new” keyword is how you indicate that you’d like a new instance of an object that’s defined by a certain class. Player is obviously the name of your class, and your parentheses indicate that this is a function call.

Now, we haven’t actually defined any function here, so what function is it calling? Well, all classes have what are called “constructor” functions, and if you don’t create one explicitly, TypeScript will create one for you. We’ll go into more detail on this in a bit.

Let’s log the variable and see what it looks like.

You’ll see that Node.js is actually very helpful here, showing us the class name, and then the empty curly brackets will contain the data in our object once we actually start adding some fields to our class.

So, how do we add things to our Player class?

The first and easiest way is to simply write the name of the variable, and then assign to it whatever value you want.

Here we’ll say each player needs and x and a y position, so we’ll set both of those equal to zero.

If we run our program again, you’ll see that I have a player with an x and y of 0. Excellent!

These fields are just like the fields of any other object. For example, I can set “p.x” equal to 1, and that will do what you expect.

A note to those from other programming languages: Fields in TypeScript are all publicly accessible by default.

Just to prove to you that we have an isolated instance of our object, let’s create a second variable called p2, and log that one out as well.

And you’ll see it’s only our original object that has changed, just as we would expect.

Going back to our class definition, note that if you wanted to be explicit about the type that you wanted a field to be, you can indicate that in the usual way by simply writing a colon and then the type that you’d like.

What we’ve done so far works well for fields that will always start off at some default value, but what about a case where there isn’t necessarily a correct default value?

For example, let’s say we wanted to give each Player a name. We’ll add the name field and say that it should be a string.

But, what’s a good default for that? Well, an empty string might not make much sense there. Neither would a random name. Plus then we’d have to remember to change it properly after instantiating it.

Probably what makes most sense would be for us to force the name to be specified at the creation of the object. We mentioned previously that there was a constructor function being created for us, let’s define it ourselves now so that we can use it to require a name.

You can do that by going to the class definition, typing “constructor”, parentheses and then curly brackets for the constructor function body.

We’ll add “name” as a parameter to this function, and we’ll assign “this.name” equal to the “name” parameter.

You’ll see that now TypeScript is giving us errors about the places where we’re creating instances of this class because we’re not providing a name.

Let’s name the first one “Tyler” and the second one “Fred”. And now TypeScript is happy again.

A couple points of clarification on constructor functions. First, the special “this” keyword refers to the particular instance of the class that you’re currently working with. Here in the constructor, that’s the newly created object that we’re building up.

Second, the way these default values for x and y are set by TypeScript is by moving the assignment to the very top of your constructor function.

So assigning a value to them on the same line as their definition is directly equivalent to writing “this.x = 0” at the top of the constructor function.

In general, since assigning the default value where the field is declared is more compact than the alternative, I tend to favor doing that in my own code. If you prefer to leave the declaration as only the types, and initialize all the defaults in the constructor that’s a perfectly valid stylistic choice.

Note that if you’re initializing something in the constructor, the field declaration must have a type on it, because otherwise TypeScript will not be able to infer what type it is that you want.

Okay, so we’ve got some data that we’re holding in our objects, let’s create a function to act on that data.

Let’s say we don’t think the output of console.log is quite to our liking, so we’d like some other way of displaying our Players.

Well, we can create a new method by specifying the name, the arguments in parentheses, and then we can specify the return type with a colon after the parentheses.

And we’ll type out a call to console.log() that formats things a little more nicely.

And we’ll run that, and hey! Things are looking pretty good.

One thing you’ll notice down below is that the list of functions that are part of an object aren’t included in the default console.log() output in Node.js. Generally this is fine, since the data will be more important to see, but this is something you should be aware of.

Let’s tie things into the previous video by showing how we can make interfaces and classes work together.

Since we’re working on a game here, I imagine we’ll have a number of different types of objects that we’ll want to treat generically in some kind of way. Perhaps we’ll use some common fields to do collision detection or to draw them or whatever else.

To define what we need, we can create an interface listing what we’ll need from each game object.

Let’s say this is a 3D game, so we’ll need an x y and z position.

In order to indicate that we want our player to be compatible with this interface, we can use the “implements” keyword followed by the name of the interface. Note that if you had multiple interfaces, you can separate them by commas.

Now we’re getting an error about our player not implementing the interface correctly because it doesn’t have the “z” field, so let’s add that.

And now everything is good.

Down lower in the code, we can create an array of GameObjects, and put our two player objects in that array. Since our Player class properly implements the GameObject interface, TypeScript is perfectly happy with this.

Note that the “implements” keyword on our class definition isn’t actually required as you might be used to from other languages. If we remove it, everything is still fine. The difference here is that if we were to remove our “z” field, we now get an error down in our array rather than on the class itself.

So why would you want to use the implements keyword if you can get away without it?

The biggest reason is that it will tell you while you’re focused on just the class definition itself whether or not the class properly implements the interface.

It also serves as great documentation for your future self and other programmers that the specific class is intentionally conforming to some interface.

A third reason, is that some frameworks like Angular will check objects for specific methods. If they exist, Angular will call them, if they don’t, Angular won’t. Including the “implements” keyword and the relevant interface for that specific Angular feature will cause TypeScript to validate that you are actually including that method properly. If not, you’ll get an error, which is helpful.

Okay, we’ll pause here and in future videos we’ll be covering things like inheritance, private, public, and protected fields, static properties, accessors, the nitty gritty details on the implementation details behind classes, and much more.

If you’ve enjoyed this video and want to go deeper, I’m working on a course on TypeScript. You can find it at https://typescriptbyexample.com I’m still working on it but if you scroll down to the bottom of the page leave your email address there I’ll shoot you an email when that course is released.

That’s it for this video! Thank you so much for watching, I will see you in the next one.

Learn TypeScript #4, Interfaces

Transcript:

Hello, everybody! In today’s video I wanted to show you interfaces, what they are, how to use them, etc. So let’s get started!

What exactly are interfaces? Well, interfaces are a way of ensuring that the objects you’re passing around throughout your program conform to your expectations. You can describe the shape of a paricular object, and the types of all its fields, and TypeScript will enforce that your expectations for that object match reality.

This becomes immensely valuable once your program starts to grow past even a few dozen lines long.

Interfaces are one of the basic building blocks of TypeScript code, and you’ll see them used everywhere. Here I’ve opened a typings file that’s internal to TypeScript itself, and if we search for interfaces we get 86 results just in this file alone! So they’re definitely something you want to be familiar with.

Interfaces pair really well with classes in TypeScript, and we’ll be going into detail on classes in a future video.

Let’s start with some simple examples of an interface. Say we’re writing a web app; we’ll likely have lots of different code that will need to work with user data, so user data seems like a good candidate for an interface.

We’ll create an interface using the interface keyword, followed by the name we want for it. I’ll call this User.

Then we’ll define the fields a user should have. Let’s say we want the name as a string, the time of their first visit as a Date, and their role is going to be either a “admin”, a “manager”, or a “employee”.

That’s a complete interface!

Let’s create a variable that uses this new interface. Like any other variable, we’ll give it name write a colon and then the type, which in this case will be the name of our interface.

We’ll then put in a name, create a new Date object which will hold the current time, and we’ll say the role is “admin”.

So what do interfaces look like when they’re compiled? Well, TypeScript interfaces are “zero cost”, which means when you compile your code they completely go away.

If we open up our compiled code, you’ll see that there’s no trace of our interface to be found.

This means that all the enforcement of the interfaces happens at compile time, and when your program is running, it’s not going to be slowed down by interfaces in any way.

On the other hand, if you’re receiving data from some external source that you’ve defined an interface for, TypeScript won’t be able to tell you if that external source has given you valid data.

So make sure that you write your own validation code in that case to prevent any runtime errors.

If you’ve been following along in the series so far, you’ll have seen me use the “type” keyword to create some new types that were useful for our programs.

The type keyword can describe objects in a similar way that interfaces can, but you generally won’t find it used that way.

Interfaces are the standard way of saying “here’s how I expect this object to look”, whereas the uses of the “type” keyword are more about combining various types in various ways.

You’ll see this more in the video on classes, where we’ll have classes declare that they “implement” certain interfaces, and TypeScript will ensure that they actually do.

In general, if you need to create a type for some specific object, use an interface. Otherwise use the type keyword.

One interesting thing you can do is create interfaces that are based off of other interfaces.

Let’s say we actually want to store some additional data on admin users. What we can do is say “interface AdminUser extends User”.

Currently I haven’t yet listed any fields. But if I were to change my use of type User to type AdminUser, TypeScript will continue to be happy because all the types from User have carried down to AdminUser.

For AdminUsers, let’s add an array of things that they have access to.

You’ll see that I now have this red underline here because the access property is missing. If we give this user access to a few things, TypeScript will be happy again.

An interesting thing interfaces can do is override fields that are defined in interfaces that they’ve extended.

A good example of this would be the “role” field. On AdminUsers, it’s always going to be “admin”, so let’s specify that.

Now if I try to change myself to an employee from an admin, you’ll see that TypeScript is now throwing an error.

Overriding fields like this is pretty useful, and you may see it in TypeScript code you encounter in the wild every now and again.

Another thing interfaces can specify is functions that should be a part of them. Let’s say for our AdminUsers we want some way of logging their name to the console.

You’ll simply write the function name, parentheses, the argument list and then the expected return value of the function.

Since it’s just an interface, we don’t provide any implementation for this function, that’s left up to the actual instances of the object.

We’ll add a function down here to satisfy the interface, and TypeScript is happy with us again.

Note that if we do something like alter the expected return type, TypeScript will helpfully indicate the error so that we know to update the usages of that interface.

Another very helpful thing you can do with interfaces is define optional properties. So let’s define an optional property called “jobTitle”. All that it takes to do that is to add a question mark at the end of the name. And you’ll see that since it’s optional, TypeScript isn’t giving us an error about the jobTitle field being missing from our user object.

Of course, it will give us an error if we define the field with the wrong type.

If you have strictNullChecks turned on, an optional field can only be the type you specify or undefined. It can’t be null. If you need null as a valid value, you’ll need to explicitly spell that out in the type definition.

If you want to prevent people from modifying an object that implements a certain interface, you can describe certain properties as “read only” by prefixing them with the “readonly” keyword.

Let’s create a variant of our User interface that is all readonly called ReadonlyUser. Perhaps we want to make sure that our user interface code is only displaying users, rather than modifying them in any way.

So now if we create a new ReadonlyUser, TypeScript will give us errors if we try to modify any of the fields.

Note that this is a fairly weak guarantee, however. If we create a variable of type User and assign our ReadonlyUser to it, TypeScript will be perfectly happy doing so, because all the necessary fields are present. But now we can modify the ReadonlyUser by way of this plain User variable.

So think of readonly as a light helper or as a bit of documentation to other programmers, rather than a strict guarantee that the field marked as read only will never change.

Moving on: a common mistake that you might make while using an interface is making a typo on one of the field names. TypeScript has a feature called “excess property checks” which aims to help prevent this kind of mistake.

Looking at our AdminUser here, let’s typo the “name” field.

TypeScript is now giving us an error that this unknown field does not exist on type AdminUser, which helpfully let’s us catch our typo.

However, the excess property checks only happen on object literals. So, that’s situations like this where we’re assigning directly to a variable, or if you were to pass an object literal to a function that’s expecting an interface, TypeScript would do these checks as well.

However, if you’re pulling in a variable in some other way, the excess property checks aren’t going to happen.

To show you what I mean, let’s change our variable declaration a little bit, and add a typo on the jobTitle field.

We’ll then create a new variable of type AdminUser and assign our previously created variable to it.

Since excess property checks won’t be used in this situation, TypeScript is only guaranteeing that the minimum required fields are present, which they are. The field with the typo is just ignored as extra data which isn’t important to the interface.

This problem very well illustrates a general principle of how TypeScript interfaces work: TypeScript will guarantee that the object has at least the fields you describe. They might have more fields, they might have extra functions, they might have none of their optional properties, they might have whatever else. But you can be sure that they have at least what you specified.

That’s especially useful to know if you’re going to do something like loop over all the fields in an object. If you do that, TypeScript is only guaranteeing that you’ll see the minimum required fields, but you might see more things depending on where your data came from, so you’ll need to make sure your code will handle that properly.

Generally you won’t run into these kinds of issues on a day-to-day basis, but it’s really helpful to be aware of them for when you eventually do.

Moving on, let’s say you have an object that you’re using to store other objects. For example, we might want to have a mapping between a user’s ID and the user data itself. You can use what are called “index properties” to describe this sort of situation.

Let’s create a MapOfUsers, and we’ll associate a string ID to the user data.

You’ll see here in square brackets we define the type we’ll be accessing with, and then after a colon outside the square brackets, we list the type we’ll return with that access.

Let’s use this new MapOfUsers type. We can put in our existing user, and that’ll work just fine.

We could also define a new user in-line, and TypeScript will enforce that it has the proper values.

TypeScript will also enforce that you use the proper index type. Here we used strings, but we could also switch to numbers instead.

You’ll see that after making that change, TypeScript indicated an error until we switched all of our values from strings to numbers.

The final feature of interfaces I wanted to show you is the ability to modify previously declared interfaces. I don’t see this used very often, but it’s good to know that it’s something that exists.

In order to do this, you simply write out the interface keyword and the name of the interface you’re going to modify. Let’s let’s pretend that this interface user is defined in some file A and this other one is defined in some file B. Perhaps in file B we’re asking the user for their age and we want to indicate that as part of the user document.

You’ll see now down here on the user we’ve created, we’re now getting an error about age not existing. So as simple as that we added a new field to the interface. Perhaps we should mark it as optional.

You’ll want to be really careful when you do this, because adding new required properties could easily break your existing code. Your code will also become harder to follow because now your interface definition is split across multiple places, so it’s hard to get the big picture view of what the whole thing looks like.

The main use case for this feature is if you are modifying a library that you’re using, or if you’re monkey patching one of the built in JavaScript objects. Otherwise I reccomend against you using this feature.

Alright! That’s it for interfaces! Stay tuned for the upcoming video on classes, which interact with interfaces quite a bit.

If you’ve enjoyed this video and want to go deeper, I’m working on course about TypeScript. You can find it at https://typescriptbyexample.com Scroll down to the bottom and put in your email address, and I’ll let you know when the course is released.

Thank you so much for watching! I’ll see you in the next video.

Learn TypeScript #3, Enumerations (Enums)

Transcript:

Hello, everybody! In our past videos we’ve talked about the most basic types in TypeScript, today I wanted to talk about enumerations (which you might also know as enums).

If you’re familiar with enumerations from other languages like C, C++, or any other language, they’re very similar in TypeScript, though there are a few differences that you’ll see as we go over their various features.

If you’re unfamiliar with enumerations (or if you don’t use them very often) the easiest way to think of them is as a group of related constants that are important to your program.

What this means in practice is that while you’re writing code you might find some mathematical constants that you need to use, or you might find that some piece of your code can be in several distinct states, and you need to represent that in some way.

Say if you’re writing a game, an enemy might be sleeping, or it might be patrolling, or it might be fighting, and you need to represent those different states with some sort of value. The exact value doesn’t necessarily matter, the most important thing is that you can tell each state apart from the other states. This is a great place to use an enum.

Using that example of an enemy in a game, we’re going to create a simple enum called EnemyState.

You can do that by using the enum keyword, then the name of the enum (EnemyState in this case), and then listing the names separated by commas within curly brackets.

A common convention when creating enums is for the members of the enum to be all upper case, with multiple words separated by underscores. However, there’s nothing forcing you to do that. If you perfer some other way of naming them, feel free to do so.

I mentioned moments ago that enums store constants, but I didn’t actually mention any values here, just names. So what value is used for EnemyState.SLEEPING?

Well, let’s create a variable storing that value, and then we’ll console.log() it.

I’ll open a terminal here and run it.

And we get 0! Okay, so we know TypeScript selected 0 for the value of SLEEPING.

Let’s change it to PATROLLING and see what it logs.

And we get 1!

So you can see that if we don’t specify any exact value, TypeScript will automatically use 0 as the starting number, and then work its way up one by one to 1, 2, 3 and so on.

One thing that’s helpful as well is that this EnemyState enum exists as an object at run time.

So, if you’re debugging something or you’re are working with a library that defines an enum and you’re asking yourself “hey what are the valid values of this particular enumeration?” you can simply log the enum object and see the possibilities.

You can see here when we logged it, though, we got more than just a list of values. We can see that the EnemyState enum maps between both the names and the values in both directions.

So if we had some random variable that came from an enum, and we wanted to see a human-readable version of the value, we can simply do EnemyState[x] and get the name of that member of the enum as a string.

If we open up the compiled .js file, you can see how they do this.

This might be a little weird to read at first, because they’re using a trick to save space, but read it from the inside out and it should make a little more sense.

Here they’re mapping between the name of the value and the value itself. And then it turns out that when you set something equal to something else, you can use that as an expression that returns the value that just got assigned. So they’re mapping the value back to the name, just like we saw in our tests previously.

It’s helpful to know at this point that you don’t need to let TypeScript automatically choose the numbers for you. You can add an equals sign to one of the members of the enum, and then all the values that follow will increment from there. You can also specify each value explicitly if that makes more sense for your program.

Now, let’s pause for a moment and compare enums to their alternatives.

The first and most obvious alternative would be to just declare a bunch of constants as normal variables, rather than as an enum. If you’ve only got a couple constants, or if none of the constants are related, that works just fine. But if they’re related, or you’ve got a situation like EnemyState where your main goal is to simply have a few distinct values, enums are the appropriate choice.

The second alternative, which you may have seen in a prior video, is union types.

As a refresher, a union type is a list of different types separated by the pipe character. You can then assign your variable to any of the types that are listed.

This is very much like an enum, and you’ll frequently see them used like enums in TypeScript code.

Enums have two main advantages over union types: First, since it’s all just numbers behind the scenes, they will be more efficient in some cases. Second, enums are available at run time as we’ve shown previously, so if you need a list of all possible values for debugging or display purposes, enums are a great option.

However, union types are quick to write and sometimes easier to think about, so often TypeScript code you see in the wild will prefer them over enums. Which is best to use will depend on your specific use case and your own personal preferences.

There’s a couple less-used features of enums that I wanted to show you before we wrap up.

First, your enums aren’t just restricted to numerical values, you can also put strings in them.

If we open the compiled JavaScript, you’ll see that there’s no longer any numbers being set. Just our enum member names being mapped to the strings we specified in TypeScript.

Back in the TypeScript code, you’ll see that if I remove one of the strings TypeScript gives me an error. Unlike numbers where TypeScript can just take whatever you did last and add a plus one to it, TypeScript has no idea what the next string is going to be. So, if you specify string constants you’re going to need to spell them all out.

Enums with strings in them aren’t used very often, but it’s a feature that’s available to you if you’ve got some string constants you need to group together.

The final feature I wanted to talk about are what’s called “const enums”. You can create one by simply putting the “const” keyword at the start of your enum declaration.

This has the effect of replacing all references to your enum with their literal values in the compiled JavaScript code. It also gets rid of the enum object. We’ll open up the compiled code, and you’ll see the enum has disappeared.

This will result in slightly more efficient code, at the cost of being harder to debug should you ever need to. Again, this is something you won’t see used very often, but it’s another tool to add to your toolbelt.

So that’s all there is to enums! I hope you enjoyed this video! If you would like to go deeper into TypeScript I’m producing a course called TypeScript By Example and you can find that at https://typescriptbyexample.com If you scroll down to the bottom you can put in your email address to be notified when the course is released.

Thank you so much for watching! I’ll see you in the next video.

Learn TypeScript #2, The Basics of How Types Work Pt. 2

Transcript:

Alright! Hello everybody! This video is a continuation of the previous video on basic types in TypeScript. Today we’re going to go over null, undefined, using types with functions, and a few other things. If you missed the previous video, you might want to go back and watch it before you start this one.

There was one thing I wanted to clarify from the last video. I mentioned that I strongly recommended against using the “any” type unless you absolutely had to, and I wanted to explain why. The basic reason, is that when you use the “any” type, you’re opting out of nearly all the checks that TypeScript is going to do for you, so if you’re going to use “any” everywhere, you might as well not use TypeScript.

I know when I first started with TypeScript, I often found myself reaching for the “any” type, but 99 times out of 100 it wasn’t because I needed it, but because I was being too lazy to stop and think about what the type of something should be.

If you take those few seconds to stop and think, you’ll end up with a program that’s better designed, you’ll have code that’s easier to come back to and change later, and you’ll have TypeScript helping you catch errors later on down the road.

Enough about “any”, let’s talk about null and undefined. This is definitely one of the more confusing parts of JavaScript (and therefore TypeScript), so let’s talk a little bit about what null and undefined are.

You may be familiar with null from other programming languages, and the easiest way I find to think about null is that it represents the explicit lack of any value. To use our player object example, if there’s no player 2, it’s possible that we’d want our player2 variable to be null.

Undefined is very similar, as it also represents the lack of any value. The key difference here, is that any variable you never assign any value to is going to hold undefined by default (though you could also explicitly set a variable to undefined, like you can set one to null).

Generally, I see null as representing a value that is intentionally missing for whatever reason. Whereas when undefined crops up in a program, it’s often a mistake or accident rather than intentional, since it implys that somebody created a variable, but then never used it. Due to this, I recommend that if you need to indicate a lack of some value, use null rather than undefined, since it will be clearer to people interacting with your code that this was probably something intentional.

All that aside, how do we use null and undefined in our TypeScript programs?

You’ll remember from our last video that we could use “null” as a type just like string or boolean or anything else, and the same is true for undefined.

Notice that if we create a variable and don’t assign to it before we try to use it, TypeScript helpfully gives us an error.

Note also that even though variables default to undefined, we can’t actually assign the value undefined or null without specifying it as part of the type of our variable.

You’ll see we get the error “type undefined is not assignable to type string”.

We have to explicitly list it as a possible type in order for TypeScript to accept it.

You’ll see also that if we try to use this variable without checking wether the type is actually not null or undefined, TypeScript will give us an error as well, preventing us from making a common mistake where we assume a variable has been assigned some value, when really it could just as easily be null or undefined.

Now, it’s important to note that TypeScript actually has a mode where it will be less strict about the usage of null and undefined. If we open up tsconfig.json and scroll down a little bit, you’ll see that I have strict mode enabled. This enables all of the strict checks listed below it, but the one that is most relevant to us is “strictNullChecks”.

If we disable strictNullChecks, or if we disable all of the stricter checks, then TypeScript will become more lenient on us and allow us to set any variable to null or undefined.

Generally I recommend against taking the more lenient route, because it leads to a lot of potential errors where you assume something will have a proper value when it actually doesn’t. It’s much better to explicitly document using the type system that a value might be null or undefined so that the proper checks can be made.

The main case for disabling strictNullChecks is if you’re converting a large existing JavaScript project over to TypeScript and you don’t want to have to deal with all the errors TypeScript starts throwing about not checking for null or undefined, since it’s common for JavaScript developers to be more wonton about their use of null and undefined. You can still get a lot of benefits out of TypeScript without enabling strictNullChecks, but again, I recommend that you enable it.

Alright, enough of that. So far we’ve been defining a number of top-level variables, but your functions will need to have types as well in order to indicate the types of arguments they accept, and what values they return.

As an example, let’s create a super simple function that tests whether a given number is a magical number.

We’ll say that if the number we pass in is 5, then it’s a magical number, otherwise it’s not magical.

And you’ll see TypeScript is complaining at me here, saying “parameter num implicitly has type any”, which is a fancy way of saying I haven’t told it what type num is, so it is forced to assume that it could be anything.

We can remedy this using syntax identical to what we used above. After the variable name, I’ll simply put a colon and the type of the variable, which is number, and now TypeScript is happy with us again.

Not only is TypeScript happy with us, but if we go to use this function, our text editor can use the definition to display what the expected types of the function arguments are.

Another thing you can do is explictly specify what the return type of your function should be. We do this in nearly the same way as declaring variables, by placing a colon after the closing parenthesis and then the type.

So, for example, if we were like hey we should have been returning a boolean, we can specify that, and then TypeScript will let us know anywhere that we are returning the wrong value.

In general as I have said before, TypeScript is going to be really good at inferring what the types of things are. So, here we could have gotten rid of the return type and TypeScript would’ve correctly guessed that boolean was what we wanted.

However, I recommend you always type your arguments in your return values for functions, that way it’s really clear to you and your team members that the function is operating in the way you intended it to operate. And it’s really easy to just glance at the line of code where you create the function and get an idea of what you expect it to do.

One other type I wanted to mention that is related to functions is called the never type.

It’s an peculiar type that means a function will never return. The two main examples of this are functions that you call to throw an error, or functions that create an infinite loop.

For example, something you’ll see in many code bases is is a function named “fail” or “panic” or something similar. This is the function you call when things have gone horribly wrong and you need to log an error and end the program.

Since the program dies before the function fully finishes, the never type is very appropriate. It’s never going to return. You can indicate that this is the case for a particular function by specifying “never” as the return type.

Likewise, if you have a function containing an infinite loop, that will also have a return value of never. The loop never stops, so the function will never return.

Keep in mind that “never” is distinct from a function that returns no value at all. For example, many times when you’re working with graphics for a game or something else, you’ll have some code that updates those graphics on-screen. There isn’t much information this code needs to return anywhere, so it’ll successfully successfully draw the graphics, and then return nothing. For that case, TypeScript has a “void” type, which you can use to indicate that the function will return at some point, but it’s not going to return any value.

That’s it for how to use basic types, and that wraps up this video.

If you’ve enjoyed what you learned here, you might like the TypeScript course that I’m working on. Head on over to https://typescriptbyexample.com, and you can put your email address in a the bottom of the page to be notified when it’s ready.

Thanks so much for watching! See you in the next video.

Learn TypeScript #1, The Basics of How Types Work Pt. 1

Transcript:

Alright! Hello everybody! Today I wanted to go over the very basics of how types work in TypeScript so that you can start using them in the programs you write. If you don’t have TypeScript installed, please see my prior videos where I’ll show you how to do that.

I’ve started here with an empty folder in VSCode, feel free to use any text editor of your choice as you follow along. Before we get started, we’re going to open up a terminal and create a TypeScript configuration file by running “tsc –init”

We’ll change one line for now, we’ll uncomment “lib” and add “dom” and “es5”. This will let us reference objects that exist when running JavaScript in a web browser, which I’ll be using for examples later.

Now let’s create our main.ts file where we’ll be doing everything today.

And then we’re going to go to Terminal and click Run Task and do “tsc –watch” to get TypeScript compiling our files.

We’re going to start off by specifying types on simple variables. There are three ways to declare variables in TypeScript and JavaScript: var, let, and const. In terms of specifying types, they all work the same, so for this video I’ll simply be using “let”. In a future video I’ll cover the differences between var, let, and const, but like I said, for this video, it doesn’t matter.

The first thing I want to touch on before we go much further is that TypeScript is really good at figuring out what the types of things are supposed to be without you needing to explicitly declare it.

For example, you can see here in Visual Studio Code we can figure out what the type of something is by hovering our mouse over, and we see that it pops up what it knows about this variable. It says “bar: number” which means it’s already figured out that this variable is a number!

However, you’ll often want to explicitly tell TypeScript what the type of something is, so that’s what we’ll be focusing on in this video. Now, as it turns out, that same syntax VS Code showed is what you’ll use to explicitly tell TypeScript what the type of something is.

We can say here “let bar: number”, and TypeScript will start enforcing that this variable can only ever be a number.

You’ll see if we try to change the value to something else, we’ll get a red underline, an error listed below, that this string is not assignable to type number.

And, just to be clear, if we remove the “: number”, we still get the same error, because TypeScript is going to assume that once you assign a variable a value of one type, that it was probably a mistake to use any other type.

Let’s start with the what the most basic types are in TypeScript: booleans, numbers, and strings.

A boolean can be either true or false.

A number can be any number you like.

And a string can be any text that you like.

Building on top of the basic types, we can do arrays of things by specifying the type and then writing an opening and closing square bracket.

There is an alternate syntax for writing arrays, though generally I don’t see it used as often. Instead of using the square brackets, you write Array, an angle bracket, the type contained in the array, and then a closing angle bracket. This is completely the same as the other syntax used above, and like I said, this is used much less commonly so I say default to the first way unless you have a good reason to do otherwise.

There’s also a variation on arrays called a tuple. If you’re not familiar with the term, you can think of it simply as an array with a fixed number of elements.

Say there are two pieces of data you always want to store next to each other, but don’t want to use a full object for whatever reason, you could do that with a tuple. For example, we could store student test scores as a tuple containing their name and the score.

Here, Tom has a score of 97.5.

And and you can see this is expecting just a string and a number. If we tried to add something extra to this, it’s going to complain to us that we’ve created something that’s not the size that we said it would be or if we try to remove a required element TypeScript will also complain. And similarly if we use the wrong type it’ll complain as well.

To be clear, you can have an array of tuples by combining the same syntaxes we’ve just learned. So we’ll have our tuple type followed by the opening and closing square brackets.

And then we would fill it out like so, and TypeScript will enforce that all the types we use here are correct.

Declaring our own objects is done in a very similar way to arrays. Let’s say we’re making a game, and we have a player with an x position, a y position, and some health. How would we do this in TypeScript?

We’ll start by declaring our player variable, then doing a colon and some curly brackets, and inside the curly brackets we can put the names and types of the fields we want to create. You’ll see that this looks very much like the actual object value that we’ll assign to the player object.

If we accidentally put in the wrong type, or if we add a field that doesn’t exist as part of the definition, TypeScript will complain at us.

It’ll often be the case that you’ll want to re-use object definitions like this in multiple places, say if you had multiple players or various functions that manipulated player objects.

There are two ways of doing this, the first and most direct way is to use the “type” keyword to define a new name that is equal to the type we defined for players earlier.

Then we can use that name in place of the full object definition for the player.

Another option with very similar effects is to create an interface. You can do this by using the “interface” keyword followed by the name you want, and then describe the types of the fields for the object like we did earlier. We can use it as the type for our player in exactly the same way that we used the definition created by the “type” keyword.

When is it appropriate to use “type” instead of “interface”? The answer is a bit too long for this video, so stay tuned for a future one where I’ll cover that. For now, just use whichever one you feel most comfortable with.

Now, going back to our example, you can see that I can now create a second player using the same type.

And if I update the definition of the type, TypeScript will helpfully give us an error on both of them, indicating that they need to be updated to match the changes.

This feedback loop is super common in development. You’ll often notice some object needs a new field, or needs that field altered in some way. You can make that change in the central type definition, and then go through and fix each of the places where TypeScript indicates that there’s a problem. No need to worry about whether you updated every spot or not, TypeScript will let you know.

Now is a good time to mention that in the same that way we created and used our own types above, we can use existing types that are part of JavaScript or JavaScript’s environment. For example, because we included “dom” in our “lib” in tsconfig.json, we can create a variable and declare that it’s type is HTMLElement. We could then fill this variable with something from the DOM API, like document.getElementById.

Now, you’ll see here that TypeScript is complaining about our HTMLElement variable. Why is it doing that? Well, if we hover over document.getElementById, we’ll see that the type is actually “HTMLElement | null”, the pipe character there means “or”. That’s because if we specify an ID that doesn’t exist on the page, this function will return null.

Using the pipe character to join multiple types together creates what’s called a “union type”, which is simply a fancy way of saying “this variable could be a few different things”. There’s a lot more to union types, and other related features in TypeScript that we’ll cover in a future video, for now, just be aware that if you have a variable that could be a couple different types, you can list those types by separating them with the pipe character.

Okay, so that covers the basics of builtin types and defining our own types. There’s a couple of catch-all types that I want to show you.

The first one is an “object”, and that represents anything that isn’t a basic type. So it’ll be anything that isn’t a boolean or a number or a string. For example, it could be HTMLElement or our Player type, or anything else. This is a type that I don’t see used all that often, because generally you either know what the object type is, or you use the even more general “any” keyword.

“any” is exactly what it sounds like. If you say the type of your variable is “any”, you’re saything that it can hold any value possible. Generally if you’re using TypeScript, I strongly recommend against reaching for “any” unless you absolutely have to use it. Though if you’re interacting with non-TypeScript code, it can be appropriate to use “any” in order to deal with JavaScript libraries that truly can produce any value.

Next up is talking about how to handle null, undefined, and how to use types with function definitions, but this video is already running a little long, so stay tuned for part 2 of this video.

If you’re enjoying what you’re learning here, you might like the TypeScript course that I’m working on. Head on over to https://typescriptbyexample.com, and you can put your email address in the form at the bottom of the page to be notified when it’s ready.

Thank you so much for watching. I’ll see you in the next video.