Understanding Types in TypeScript
I recently read this hilarious article about what it means to be a JavaScript developer in 2016.
The summary – working with JavaScript involves knowing a lot more than JavaScript. Remember the old days where we imported jQuery through a CDN and used that to manipulate a few things in the DOM or run some AJAX requests? That’s changed.
If you’re working with any modern JavaScript framework, you’ll most likely be writing code in ES6 or TypeScript or JSX, maybe using a templating engine like handlebars. You’ll also be using module loaders (like webpack) and automation tools (like grunt or gulp) along with package managers (mainly npm).
Now even though that may sound like a lot of work, it isn’t a bad thing! These tools and high-level frameworks can save you a lot of time and trouble.
Today we’ll talk about TypeScript and how you can work with it’s types.
Quick Overview of TypeScript
Here’s what the TypeScript’s website says –
TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
Any browser. Any host. Any OS.
Now that’s definitely a mouthful. Let’s give that some context.
JavaScript was never meant to be used as the core language for building large scale web applications. In the past, building large scale applications meant working with a language like Java or C# – .net (just to name a few). Such languages usually leaned towards “strongly typed” (in a strongly typed language, each type of data is predefined, that means that once x is a string, it’ll continue to be a string in it’s current scope).
With such languages, you save a lot of time by avoiding runtime errors. But that’s not the case with JavaScript. Slam your keyboard if this has ever happened to you.
or maybe this
JavaScript was used to add a few quirks to the HTML page. It was just something you needed to use when you needed additional client side functionality. Which is why it made sense for it to be a “loosely typed” language.
But times have changed now. Today, so many developers are using JavaScript full-stack for large applications. That means that there is some room for improvement.
That’s exactly what TypeScript aims to do. The benefits of a strongly typed language that compiles to JavaScript.
Try It!
If you want to try TypeScript right now, you can install the TypeScript Compiler (tsc
) by following the quick start intructions.
Or you could just punch in the code samples without having to install any packages with the TypeScript playground.
What are Types
Types are what makes TypeScript great. They prevent many runtime errors and allow IDEs to do their magic and show you where the errors lie.
If you’ve worked with other object oriented programming languages (like Java), everything we talk about below will hit right home. If all you’ve ever known is JavaScript, read on!
Take a look at this code.
1 2 3 4 5 6 |
var x = 'something'; var y = 3; x = y; console.log(x); |
JavaScript is probably one of the very few places you can do something like this. If we were working with an object oriented language (the example below is in Java), this is what the same code would look like.
1 2 3 4 5 6 7 8 9 10 |
class Main { public static void main(String[] args) { String x = "something"; int y = 3; x = y; System.out.print(x); } } |
And this is what the console output would be.
You may say… “That just makes things harder.” And you’d be right.
For a simple program like this, it doesn’t matter, but when things more complex, you want compile errors to save you from annoying runtime errors. Let’s take a look at one more example.
We wouldn’t see a problem until our program reached this bit of the code during runtime…
1 2 3 4 5 |
var x = [1,2,3,"I don't belong here",4,5]; for (var i = 0; i < x.length; i++) { doSomethingImportantWithNumber(x[i]); } |
We’d know about the error as soon as the program would compile…
1 2 3 4 5 6 7 8 9 |
class Main { public static void main(String[] args) { int[] x = {1,2,3,"I don't belong here",4,5}; for (int i = 0; i < x.length; i++) { doSomethingImportantWithNumber(x[i]); } } } |
Imagine what will happen when this application gets larger!
Basic TypeScript Types
Even though we’ve been giving examples using Java, a more “strongly typed” language, that doesn’t mean TypeScript and Java are the same.
TypeScript is a superset of JavaScript. Which means that it allows us to work with the awesomeness of JavaScript. If you’re used to working with other object oriented languages, don’t be surprised if you see a data type that you’ve never seen before.
let
Before you can make sense of the examples below you need to know a bit more about the let
keyword.
let‘s run through this really quickly!
Here’s how you declare a variable in JavaScript.
1 |
var x = 'hello'; |
Here’s the problem…
1 2 3 4 5 6 7 8 9 10 11 12 |
function quirkyScope() { var x = 'hello'; if (true) { var x = 'bye bye'; console.log(x); } console.log(x); } quirkyScope(); |
You’d expect the output of this to be…
1 2 |
bye bye hello |
But the real output is…
1 2 |
bye bye bye bye |
That’s because JavaScript has very different scoping rules compared to other languages.
let
is a block scope variable declaration present in TypeScript (and ES6). When it compiles down to JavaScript, all it does is rename the variables to make it seem as though we are using block scope variables. Even if we aren’t.
Here’s a crash course in using let with the same example…
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// We just change 'var' to 'let' function normalScope() { let x = 'hello'; if (true) { let x = 'bye bye'; console.log(x); } console.log(x); } normalScope(); |
That gives us the expected output…
1 2 |
bye bye hello |
It also rids us of the other scope-related quirks of JavaScript. You can read more about let
here, but for now, this is all you need to know.
1.Boolean
This doesn’t need any explaining, does it? True or false?
1 |
let isReady: boolean = false; |
2. Number
All numbers in TypeScript are floating point values. This means that whole numbers, as well as decimals, come under the type number
.
1 2 |
let wholeNumber: number = 5; let decimalNumber: number = 5.25; |
TypeScript also allows us to declare hexadecimal, binary and octal literals.
1 2 3 |
let white: number = ffffff; let plusSign: number = 0101011; let octalPlusSign: number = 53; |
3. String
There are three ways to use strings in TS.
- Double Quote Strings –
let double: string = "double quotes";
- Single Quote Strings –
let single: string = 'single quote';
- Template Strings – these are special.
Let’s look at an example of declaring template strings –
1 2 3 4 |
let myFavoriteFood: string = 'banans'; let firstName: string = 'John Smith'; let templateString: string = `My name is ${firstName} and I like '${myFavoriteFood}'. No, I love "${myFavoriteFood}"`; |
There are three things you need to notice in the templateString
above –
- It is surrounded with backticks (`).
- We can use expressions within such a string using the
${expressionName}
syntax. - We can use single or double quotations within these strings.
To use backticks in template strings, just escape them with \
.
1 |
let iWantToUseBackticks: string = `Here's a backtick - `` |
4. Array
There are two ways to type protect an array…
1 2 3 4 5 |
// First method let numbersArray: number[] = [1,2,3,4,5]; // Second method let anotherArray: Array = [6,7,8,9,10]; |
But what if you wanted more than one type in your array – maybe a string and a number?
5. Tuple
These data types allow you to define an array where we know the type of a fixed number of elements. Here’s an example…
1 2 3 4 5 6 7 8 9 10 11 |
let tupleArray: [string, number, number]; tupleArray = ['add', 2, 3]; // GOOD! tupleArray = ['multiply', 3, 4]; // GOOD! tupleArray = [2, 3, 'multiply']; // ERROR! tupleArray = ['add', 2]; // ERROR! tupleArray = ['add', 2, 3, 4, 'subtract', 6] // GOOD? |
Any additional elements in the array are OK, but they must be a union type – i.e. either of the types we have specified in the tuple.
In our example, our tuple has two types – string
and number
. So any of the elements after the first three must either be a string
or a number
.
We’ll dive deeper into union types when we cover the advanced types in TypeScript in another article.
6. Enum
Enums are a friendly way of declaring numeric values. Have a look at this example where we only want to allow relatively sober people into our club to avoid any nonsense.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
enum alcoholLevel {Tipsy, Drunk, Wasted, SuperWasted}; // Allow person if they aren't drunk function guardCheck(level: alcoholLevel) { if (level > alcoholLevel.Drunk) console.log('no entry'); else console.log('enjoy your night'); } let stacyLevel: alcoholLevel = alcoholLevel.Drunk; let andreaLevel: alcoholLevel = alcoholLevel.SuperWasted; guardCheck(stacyLevel); guardCheck(andreaLevel); |
You can see how this is better than using constant or numbers directly. This makes the code much more readable and very safe.
By default, TypeScript assigns these enums values starting at 0. If for some reason we wanted to change that, we could…
1 2 3 4 5 |
// Starting at higher values enum alcoholLevel {Tipsy = 2, Drunk, Wasted, SuperWasted}; // Tipsy = 2, Drunk = 3 and so on // Different values enum alcoholLevel {Tipsy = 25, Drunk = 50, Wasted = 75, SuperWasted = 100}; |
We can also get the name of the value in the enum from the numeric value.
1 2 3 4 |
let johnDrunkPercent: number = 63.5; let roundedUpPercent: number = 75; console.log(alcoholLevel[roundedUpPercent]); // output: Wasted |
7. Any
There are cases where we may not know the types we are working with. That’s when we can use the any
type.
1 2 3 4 |
let random: any = "I can be of any type. TypeScript cannot stop me!"; // Useful in arrays where we can't predict all the values let unpredictableArray: any = ["What's the time?", 4, false] |
Any is even more flexible than a JavaScript Object
. We can’t use any methods other than those that are a part of JavaScript’s Object interface (like toString
) – even if we define them explicitly.
1 2 3 4 5 6 7 8 9 10 |
let anyObject: any = 4; anyObject.someMethod(); // GOOD! let objObject: Object = { someMethod: () => { console.log('this is some method'); } }; objObject.someMethod(); // ERROR! obObject.toString(); // GOOD! // Because toString is a part of JavaScript's object interface and all data types extend a JavaSCript object // You can read about this method here - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString |
8. Void
You use void when there is no type. This is useful when you are adding types to functions that don’t have a return value.
1 2 3 |
function logger(value: string): void { console.log(value); } |
You could use void to declare variable types as well, but that isn’t very useful because only null
and undefined
can be of the type void
.
Types on Functions
We didn’t add types to the return values in any of the functions above to keep the examples simple. Here’s how you would use functions with a return type.
1 2 3 4 |
function sayHi(): string { return 'hey'; // return 3 would give an ERROR! } |
9. Null and Undefined
null
and undefined
are subtypes of all other types. That means let value: number = null;
is allowed. You could also use them on their own like let nullVal: undefined = undefined;
. As you can see, that isn’t very useful.
So why have these types at all?
They become useful when you use the --strictNullChecks
flag. That doesn’t allow type (other than void
) to be null.
1 2 3 4 5 6 |
// With --strictNullChecks flag let val: number = null; // ERROR! let anotherVal: void = null; // OK! let thirdVal: number | null | undefined = null; // OK! |
The last example uses the union
type. We’ll talk more about that in the advanced TypeScript types (in another post).
10. Never
never
is used for values that never occur.
Wait… What?
Look at this function…
1 2 3 4 5 |
function logger(value: string): void { console.log(value); } console.log(logger('Hey there')); |
This is what the output looks like…
1 2 |
Hey there undefined |
Almost every JavaScript function returns a type – even console.log
. When we don’t explicitly return a type in a JavaScript function, we implicitly return void
.
Now take a look at something completely different…
1 2 3 4 5 6 7 |
function startLoop() { while(true) { console.log('I will never end!'); } } console.log(startLoop()); |
Our final log statement would never be executed! So the startLoop
function never
returns a type. It just goes on and on!
No type is assignable to never
other than never
itself.
You can have a look at some more examples of never
here.
Further Learning
You’ve come a really long way today! You’ve got a great grasp on TypeScript’s basic types.
But that’s not all the types TypeScript has to offer. We’ll be covering the advanced types in another article. If you can’t wait to read about them, click here.
Finally, after you’ve meddled around with some code, gone through the official docs, you may want to backtrack and look at an Introduction to TypeScript from the guys at Microsoft.
Who said learning has to be linear!
Happy “typing” (pun intended) 😉