TypeScript: 5 Areas to Master


Together, with some help from our favourite heroes and villains in the Marvel Universe let’s look at the 5 key areas to master

TypeScript is popular. A recent survey carried out by Stack Overflow places TypeScript as the second most loved language after Rust. That's up 8 places on the 2019 rating.

What's more, you’re in good company if you are using it. TypeScript is at the moment used by Microsoft, Asana, Lyft, Slack, all Angular 2+ developers, many React & Vue.js developers, and thousands of other companies, so it has been and continues to be battle-tested.

I’m a believer in the 80/20 rule and think a case can be made that having a great understanding of 20% of the language can allow you to solve 80% of the daily problems you come across.

Together, with some help from our favourite heroes and villains in the Marvel Universe let's look at the 5 key areas to master that make up that 80%.

1. Nail down the Types

Before we go any further, let’s address the Type in TypeScript, which is often categorized into 2 groups — primitive and complex types.

All primitive data types in JavaScript are supported by TypeScript.

let Bool: boolean = CaptainMarvel.name === "Carol Danvers";
let Number: number = 2022; // Planned release for Captain Marvel 2
let String: string = "Binary Ignition"; // Captain Marvels superpower
let Undefined: undefined = undefined;// Like where she was for most of EndGame
let Null: null = null; // Like Captain Marvels Weaknesses
// Yet to ever use these, but when the days comes, I'll be ready
let bigInt: bigint = 9007199254740993n
let Symbol: symbol = Symbol('foo');

Inferring and explicitly declaring types

What we see above is us declaring the type after a value and colon. These are explicit types. If you don't explicitly declare the type, Typescript will infer the type for you. What does this mean? Well, see below…

// The qoute that broke a million hearts
let string = "I love you"
string = "3000"
// Error: Type 'string' is not assignable to type 'number

That's right, Typescript is doing some spooky stuff in the background and performs some type checking for you without you needing to do anything.

Remember Explicit and Infer Types, It can be very helpful, but can also leave you scratching your head wondering where the error is coming from.

It's in complex types where some of the real magic happens. Let's look at the most common one we come across.

Object types

In JavaScript, the fundamental way that we group and pass around data is through objects. In TypeScript, we represent those through object types. We can achieve this by putting some primitive types together.

// We can represent an object type like this
avenger:{name:string; superhuman:boolean}
// Or we could use the type alias, which when dealing with larger objects you may deam necessarytype Avenger: {
name:string;
superhuman:boolean;
}
// this allows us to do do exciting stuff like this
const isSuperHuman = (avenger: Avenger) => {
console.log( avenger.name + (avenger.superhuman ? " is" : " is
not") + " superhuman");
}

Any

The any type allows us to assign literally any particular value to that variable, simulating plain Javascript in a way. It is useful when there are no other options, or if no type definitions are available. For example a 3rd party API, or package you’re working with.

let infinityStones:string = "Six"
infinityStones = 6
// Error: Type 'number' is not assignable to type 'string'
// No longer a problem when I exchange string for an any type
let infinityStones:any = "Six"
infinityStones = 6

Arrays and Tuples

One of the harder things to grasp is the concept of tuples in Typescript.

// This is an array
let hero: string[] = ['Ironman', '49', 'true', '3000'];

// This is a tuple
let hero: [string, number, boolean, number] = ['Ironman', 49, true, 3000];

An array that has a fixed number of value types arranged in a particular sequence is called a Tuple.

Tuples maintain strict length and ordering enforcements. I like to imagine tuples as their own strict and unique types, that I create. I cannot extend them like I can an array type for instance.

It’s like putting handcuffs on an array, so they can’t pick up anything else.

let hero: [string, number, boolean, number] = ['Hulk', 49, true, 3000];

// A tuple can even keep the Hulk under control

hero[3] = 'Smash'; // Error: Type 'string' is not assignable to type 'number'

Now, what happens if I need to take off the handcuffs and extend my array, Well that's possible using the concat method. This will turn your tuple into an any[] type. Which is obviously less strict, but it's better than a compiler error right?

For me, I would like to know when I am changing the type of my tuple to any[]. Since if my tuple is not extendable enough to cover the schema I require, maybe I should rethink my Tuple or maybe I shouldn't be using a tuple at all?

Either way, I think there’s space here for healthy debate.

// This is a tuple
const ThanosFavoriteThings: [ string, number, string] = ['Gamora', 6, 'Infinity Stones'];
ThanosFavoriteThings[3] = 'Power'
// Error: Type '"Power"' is not assignable to type 'undefined'
// Error: Tuple type '[string, number, string]' of length '3' has no element at index '3'.
// Calling .concat() on a tuple returns an array with a type of any[]
const AllThingsThanosLikes = ThanosFavoriteThings.concat(['Power']);
// An array is expandable
AllThingsThanosLikes[5] = 'Gardening';
console.log(AllThingsThanosLikes);
// This will print ["Gamora", 6, "Infinity Stones", "Power", "Gardening"]

2. The joy of Enums

Those of you coming from a traditional server-side language may have been using these for a while. It is good practice to use Enums anywhere where a type can accept any of a predefined set of values.

Think “North, South, East, West” or “Male, Female, Non-Binary” or “The Avengers, Age of Ultron, Infinity War, Endgame”. I like to use them when creating solutions for Multi-choice dropdowns or checkbox selection.

An Enum has enumerators which auto-increment or can be set to a string or integer. I often prefer using a string value, although they don’t auto-increment, they can be helpful when debugging, offering improved readability.

// Auto Increment
enum Movies {
TheAvengers // '0': "The Avengers "
AgeOfUltron // '1': "Age of Ultron"
InfinityWar // '2': "Infinity War "
Endgame // '3': "Endgame "
}

// Set Integer Value
enum MovieRelease {
TheAvengers = 2012
AgeOfUltron = 2015
InfinityWar = 2018
Endgame = 2019
}

// Set String Value
enum MovieName {
TheAvengers = "The Avengers"
AgeOffUltron = "Age of Ultron"
InfinityWar = "Infinity War"
Endgame = "Endgame"
}

const MovieName: MovieName = MovieName.InfinityWar
const MovieRelease: MovieRelease = MovieName.InfinityWar
console.log(`${MovieName} was released in ${MovieRelease}`)
// Logs "Infinity War was released in 2018"

3. When you need a little more flexibility

When working with existing Javascript where you may want to re-assign an existing variable. Typescript provides us with 2 solutions.

The any type, which we mentioned earlier and can be assigned to any variable.

Or a Union type that can be created by adding a pipe between your types. It allows us to do some very interesting things

like this…

let Thor: string | number | boolean;
Thor = 'Mjolnir'; // Thors famous hammer
Thor = 8; // The number of marvel movies he has featured
Thor = 'Avengers 4' // The next movie Thor has a role in
Thor = Thor > Hulk // The great debate continues

or this…

// Lets create a mini battle game, working of the following pretenceScarlet Witch > Dr. Strange 
// I think Scarlet Witch beats Strange, since she nearly bet Thanos on her own
Dr. Strange > Dormmamu
// He's already managed to outsmart the ruler of the dark dimension once
Dormmamu < Scalet Witch
// Dormmamu kicks ass here, In fairness I think he beats most
/
/ This is a union of string literal types
type BattleCharacter = 'Scarlet Witch' | 'Dormmamu' | 'Dr. Strange';
const Battle = (choice: BattleCharacter): void => {
let result: string = '';
switch (choice) {
case 'Scarlet Witch':
result = 'Dormmamu';
break;
case 'Dormmamu':
result = 'Dr. Strange';
break;
case 'Dr. Strange':
result = 'Scarlet Witch';
break;
}
console.log('Me: ', result);
}
const number = Math.floor(Math.random()*3);let BattleCharacter: [BattleCharacter, BattleCharacter, BattleCharacter] = ['Scarlet Witch' | 'Dormmamu' | 'Dr. Strange'];Battle(choices[number]);

4. Scaling our new found control

Interfaces define objects consisting of all the types we have looked at up to this point.

It is with interfaces that we can really start scaling the control we now have over our object modal values, using them to ensure that the correct data is passed to properties and functions.

That's great but what's even better is that interfaces are composable. TypeScript allows you to nest interfaces within an interface or inherit from another interface.

// We can turn this...
interface Avenger {
id: number;
name: string;
stats: {
weapon: string;
stength: number;
superHuman: boolean;
}
movies: {
[filmId: string]: {
name: string;
releaseDate: number;
rating: number;
}
}
}
// into this...
interface Avenger {
id: number;
name: string;
stats: Stats;
movies: { [filmId: string] : Movie}}
interface Stats {
weapon: string;
stength: number;
superHuman: boolean;
}
interface Movie {
name: string;
releaseDate: number;
rating: number;
}

The resulting code is a little longer, but we addressed the problems with a big interface. We can now read our code easier with named types and we can reuse the smaller interfaces in other places in our code.

Composing types together is an essential way to keep our code organized and flexible.

5. Bringing it all together with a look at functions

Functions are the fundamental building blocks of any Javascript application, so naturally, they are going to play an important role in Typescript where we will want to use it for readability, type checking, debugging and preventing bugs in the first place.

Parameter types

Typescript allows us to provide type annotations to parameters that we pass into our functions, which will mean it also needs to be able to deal with optional parameters.

To indicate that a parameter is intentionally optional, we add an ? operator after its name. This tells TypeScript that the parameter is allowed to be undefined and doesn’t always have to be provided. If no parameter is selected it will infer the parameter type, the same way it infers the type of an initialized variable to be the same as the type of its initial value.

Function return types

So we can Type check what goes into our function, you better believe we can do the same for what comes out. Again Typescript will infer the return type of the function if you don't declare it, otherwise, we can explicitly specify return types for functions, including functions that don’t return anything.

This is done with a type value of void.

Void is a little like the opposite of any and is the absence of having any type at all. You may commonly see this as the return type of a function that does not return a value. Generally, if a function does not have a return statement, you’ll likely want to use this.

class Avenger {

constructor(obj: Avenger){
this.name = obj.name;
this.stats = obj.stats;
this.movies = obj.movies;
}

attack():void {
const x = this.stats.superHuman ?
this.stats.strength * 2 : this.stats.strength
alert(this.name + ' can use ' + this.stats.weapon + 'to cause' + x + 'damage')
}


bestMovie(): string {
const movie = this.movies.reduce((prev, movie) => {
return (prev === undefined || movie.rating > prev.rating) ?
movie : prev
})
return movie.name
}
}
const IronMan = new Avenger({
name: "Tony Stark",
stats: {
weapon: "Rockets",
strength: 8,
superHuman: false
},
movies: [
{ name: "Iron Man", releaseDate: 2008, rating: 7.9 },
{ name: "Iron Man 2", releaseDate: 2010, rating: 7.0 },
{ name: "Iron Man 3", releaseDate: 2013, rating: 7.2 }
]
})

And there we have it. I hope you have found this useful. Thank you for reading. If you enjoyed the Avengers theme, you may also enjoy some of the swag ideas we created at !!nerdy