DEV Community

Cover image for Let's talk about Typescript
IanMcbull
IanMcbull

Posted on • Edited on

Let's talk about Typescript

JavaScript is now one of the most used programming languages today thanks to not only its flexibility, but also as a result of the internet boom.

We access the internet using web browsers and JavaScript comes packaged with your web browser. At some point you're going to interact with JavaScript either as an end-user or a developer whether it's on your smartphone, or your smart TV, basically anything that will at some point talk to the internet.

The "WHY" behind Typescript

Question mark image

JavaScript isn't perfect though, in fact, it can get pretty frustrating to work with. JavaScript is a dynamically typed language. This means JavaScript will try to help you out even when you haven't asked it to.

Let's look at an example:


let numberOne = 1;
let stringOne = "One"
let sum = numberOne + stringOne;
console.log(sum)
// Output
// 1One

Enter fullscreen mode Exit fullscreen mode

Intuition will tell you that this operation should not be possible. How can we add a number to a string? JavaScript will take a look at it and say "Hey human, I can help you with that", then proceed to change your number value into a string.

While there are some benefits to this kind of behavior, it more often than not causes more harm than good. JavaScript will not complain about this behavior, until it absolutely has to.

As a JavaScript developer, you've probably encountered this behavior before. For example passing in the wrong data type as a parameter to a function.


const squareMe = (number) =>{
  return number * number;
}

// Let's call the function but pass in a string instead of a number
console.log(squareMe("five"))

// Output
// NaN

Enter fullscreen mode Exit fullscreen mode

👋 The most common kinds of errors programmers write are type errors, where the wrong type of value was used.

Wouldn't it be nice if we could accurately determine how our code would execute without having to worry about Javascript playing judge, jury and executioner.

Typescript to the rescue

Superman Walking

Up to this point, we've been writing dynamic code. This is referred to as dynamic typing, where the language can decide to fill in the gaps when it needs to.
Typescript offers us a way out. Rather a way to limit the dynamic nature of JavaScript by allowing us to use static typing instead.

Static typing in a nutshell, is the opposite of dynamic typing. Instead of being as vague as possible when writing our code, we try to be as explicit as possible. Let's declare a pair of variables using typescript.


 let numberOne:string = "One";
 let numberTwo:number = 2;  

Enter fullscreen mode Exit fullscreen mode

This is what typescript allows us to do. We are explicitly declaring the data types our variables can hold. If we try to re-assign the variables with values that don't match the data types specified, typescript will complain.


numberOne = 1; // Error: type "string" is not assignable to number
numberTwo ="2"; // Error: type "number" is not assignable to string

Enter fullscreen mode Exit fullscreen mode

You'll get immediate feedback from typescript write in your code editor. This type checking functionality is what has propelled typescript to be one of the most loved languages in recent years.

The graph below is Stack Overflows' Annual Developer Report 2022 for the most loved programming languages.

Stack overflow Annual developer report 2022

So Typescript in a nutshell, is still JavaScript, but a statically typed or strongly typed JavaScript.

Now let's begin to look at some of the features that are available to us when using typescript starting with the tsc.

The tsc

TSC stands for typescript compiler. The typescript compiler is the brains behind typescript. It gives us a plethora of options to choose from when it comes to configuration options.

Before we can even interact with typescript though, we need to first install it on our machines.

npm i typescript -g

or

yarn add typescript

Confirm you have it installed by running tsc -v on your terminal.


tsc -v 
Version 4.7.4

Enter fullscreen mode Exit fullscreen mode

To initialize a typescript config file, run the following command in your terminal window

tsc --init

This will generate a configuration file for you that the typescript compiler will then use to run your typescript code.

Common Configuration options

They're a lot of options to choose from, but we won't dive into the details of what they all mean. We will however look at the common configuration options that you will more often than not be interacting with.

noImplicitAny

This configuration option ensures that types are not inferred. This has the added advantage of ensuring that we always pass in the correct types when executing our program. By default, tsc will infer the any type to your variables. This is the same behaviour vanilla javascript gives you.
With the implicitAny option enabled, variables inferred with the any type will issue an error.

strictNullChecks

This option allows us to handle operations that return null or undefined more elegantly. We've all at one point or the other forgotten that a particular function doesn't return anything and tried to operate on them.

target

This option refers to the version that typescript will emit after transpiling your code to vanilla javascript. Remember your browser doesn't understand Typescript and that's why we need to specify the target version of javascript.

module

Specify the JS Module System that you'd like to use.E.g commonJS, AMD, ECMAScript

strict

The strict option specifies how strict tsc should be when it comes to type checking. As good practice, you should always have this option turned on.

The basics

The tsc is responsible for checking your code before executing. Let's look at an example of valid JavaScript code, that shouldn't intuitively work, but does.

function shineMyName(firstName){
   console.log(`This little ${firstName} of mine, I'm gonna let it shine..`)
}
console.log(shineMyName())
// Output
// This little undefined of mine, I'm gonna let it shine.
Enter fullscreen mode Exit fullscreen mode

That doesn't look right does it? We forgot to pass in the firstName parameter to our function, but we were still able to run our code.

If you try to run the same code in typescript, you'll get immediate feedback from the tsc complaining that you didn't pass in all the parameters.


 function shineMyName(firstName){
   console.log(`This little ${firstName} of mine, I'm gonna let it shine..`)
}
console.log(shineMyName())
// Expected 1 argument  but got 0 ts(2554)
// An argument for firstName was not provided

Enter fullscreen mode Exit fullscreen mode

Let's add more type-checking capabilities by explicitly defining the data types.


function shineMyName(firstName:string){
   console.log(`This little ${firstName} of mine, I'm gonna let it shine..`)
}
console.log(shineMyName("Bob"))

Enter fullscreen mode Exit fullscreen mode

This is referred to as type annotating. Let's look at a few more example of type annotation.


let lastName: string = "Marley";
let age:number = 45;

Enter fullscreen mode Exit fullscreen mode

This method of annotating is called explicit typing where we explicitly define the type of value.

Typescript also has the ability to infer types. This is called type inference

let lastName = "Marley"; // string 
let age = 45; // Number
Enter fullscreen mode Exit fullscreen mode

💡 When you don't explicitly annotate the types, typescript will infer the types for you. This is called implicit annotating.

Primitive types in Typescript

  • String
  • number
  • boolean
  • null
  • undefined
  • void
  • never
  • any
  • symbol
  • bigint
let firstName: string = "Mark";
let age:number = 30;
let isCool:boolean = true;
let bigNum:bigint = 100n;
let weDintKnow:any = "I don't know";
let maybeWeknow:unknown = "I might know";
let notDefined:undefined = undefined;
let nulling:null = null;
let never:never;
let symbol:symbol = Symbol("symbol");
let voided:void = undefined;
Enter fullscreen mode Exit fullscreen mode

Defining Types in Typescript

Arrays

To specify an array in typescript, we use the syntax type[]. You start by defining the data types that the array will include followed by a pair of square brackets.


let lyrics: string[] = ["Hey", "I", "Just", "Met", "you", "and", "this","is","crazy","but","here's","number","so","call","me","maybe"]

Enter fullscreen mode Exit fullscreen mode

We first define the types of values that should be stored in our array. In our example, we can only have strings in our array.

Tuples

A tuple resembles an array but with a few differences:

  • The length of the tuple is fixed,
  • The tuple can have a mix of data types,

let rgb:[number,number,number] = [239,120,200]

Enter fullscreen mode Exit fullscreen mode
Functions

You'll want to check the type of values being passed to our functions and define the type of value that the function should return.
We can do this by annotating our arguments as well as defining the type of value to be returned.


  function lyricsMatch(lyrics:string[]):string{
    return lyrics.join(' ');
  }

console.log(lyricsMatch(["Hey", "I", "Just", "met", "you","," "and", "this","is","crazy","but","here's","number","so","call","me","maybe"]))

// Hey I just met you, and this is crazy but here's my number, so call me maybe
Enter fullscreen mode Exit fullscreen mode

We're explicitly telling typescript, that our function will take a single argument that has to be an array of string(s). We are also indicating that our return value will be a string as well.

If we try to pass in a parameter that isn't an array of string(s), the tsc will complain.


lyricsMatch("I'm never gonna dance again")
// Argument of type string is not assignable to string[]

Enter fullscreen mode Exit fullscreen mode
Union Types

A union type is a combination of one or more types. Let's look at an example.


 function returnAge(age:string|number){
  return age;
}

console.log(returnAge(20))
console.log(returnAge("20"))
// The age argument can either be number or string type
// Both calls to `returnAge` will execute successfully.

Enter fullscreen mode Exit fullscreen mode

Something to keep in mind though is that when you use unions, you can't use methods that are available to one of the data types only.

For example we can't do something like age.toUpperCase().
If the parameter that we passed in was a number, it wouldn't work. To get around this, we use narrowing.


 function returnAge(age:string|number){
  if(typeof age === "string"){
    return age.toUpperCase()
  }
  return age;
}

Enter fullscreen mode Exit fullscreen mode

Narrowing simply means we try to deduce the data type that was passed in. If it's of type string, then we know we can use the toUpperCase() method on it.

Type aliases

Let's define an object shape


{
  name:string,
  material:string,
  status?:boolean
}

Enter fullscreen mode Exit fullscreen mode

Now let's assume we want to keep reusing this shape throughout our code base. We can save this blueprint using type aliases.

 type glassJar = {
  name:string,
  material:string,
  status?:boolean
 }
Enter fullscreen mode Exit fullscreen mode

Glass Jar

Now we can reuse this object shape anywhere in our code base without having to keep rewriting the shape itself.

const jarOne: glassJar = {
  name:"Mikes Jar",
  material:"glass",
}
Enter fullscreen mode Exit fullscreen mode

You can use type aliases to define any type of data structure and not just objects.

type ID = number | string;
type userIDs = string[] | number[];
Enter fullscreen mode Exit fullscreen mode
Interfaces

An interface is an alternative syntax that we can use to create objects.

interface glassJar:{
  name:string,
  material:string,
  status?:boolean
 }

const jarOne: glassJar = {
  name:"Mikes Jar",
  material:"glass",
}
Enter fullscreen mode Exit fullscreen mode

Literal Types

To explain type literals, we need to think about let,var and const and how typescript interprets these values.

We know that using let and var allows us to change the values at a later time.

 let buzz = "Buzz Light Year";
 var woody = "woody";

 // How typescript represents this: let woody:string
// let buzz:string`
Enter fullscreen mode Exit fullscreen mode

We are telling the compiler that we want the values represented by the two variables to be any string values.

But what about declaring types with const. These are values that we don't plan on ever-changing.

const peterParker = "Spidey";

// How typescript represents this:
// const peterParker:"spidey"
Enter fullscreen mode Exit fullscreen mode

Because we can only represent a single string value, the variable is assigned the type "spidey".

Conclusion

💡The biggest advantage of using typescript is the ability to catch type related bugs at compile time.

I only scratched the surface in terms of what typescript can do but hopefully this post can push you to adopt it if you haven't already. It's a great tool that will boost the quality of the code that you write.

Thanks for reading and happy coding. 👋

Top comments (0)