Post

Typescript Pt. 1

What is TypeScript?

TypeScript is a superset of JavaScript. All JavaScript code is valid TypeScript code.

  • It adds types to JavaScript.
  • It is a strongly typed language.
  • It is compiled to JavaScript.

TypeScript helps us identify errors at compile time through the process of static type checking. This means that the TypeScript compiler will check our code for errors before the code gets executed (without running it).

What is an Interface?

Interface defines the structure of an object. In our example, we have a Person interface that defines the structure of a person object.

1
2
3
4
5
6
7
8
9
interface Person {
  name: string;
  age: number;
}

let person: Person = {
  name: "John",
  age: 30,
};

What is a Type?

In TypeScript, a type is a way to refer to the different properties and functions that a value has. In our example, we have a Todo type that refers to the different properties that a todo object has.

1
2
3
4
5
type Todo = {
  id: number;
  title: string;
  completed: boolean;
};

Another example is a variable of type string and with a value of hello.

1
const hello: string = "hello";

This means that the variable hello can only have a value of type string including all the properties and functions that a string has. For example, the toUpperCase() function.

1
2
3
const hello: string = "hello";

hello.toUpperCase();

Every value in TypeScript has a type. For example, the value true has a type of boolean.

1
const isDone: boolean = true;

What are Type categories?

Types in TypeScript can be divided into two categories:

  • Primitive types (number, boolean, void, undefined, string, symbol, null)
  • Object types (functions, arrays, classes, objects)

Types are used by the TypeScript compiler to analyze our code for errors. For example, if we try to assign a value of type string to a variable of type number, the TypeScript compiler will throw an error.

1
const age: number = "hello"; // Error: Type '"hello"' is not assignable to type 'number'.

What are Type annotations?

These are used to tell TypeScript what type of value a variable will refer to. For example, we have a variable age that refers to a value of type number.

1
let age: number;
  • when we declare a variable and add a semicolon followed by a type annotation, we are telling TypeScript that the variable age will always be assigned a value of type number.
1
2
let age: number;
age = 30;
  • If later on, we try to assign a value of type string to the variable age, the TypeScript compiler will throw an error.
1
2
3
let age: number;
age = 30;
age = "hello"; // Error: Type '"hello"' is not assignable to type 'number'.
  • The following is an example of an array variable with a type annotation.
1
let names: string[] = ["John", "Jane", "Mary"];

This means that the variable names can only have an array of strings as its value.

  • Similarly, we can declare a variable which can only have numbers as its value.
1
let ages: number[] = [30, 40, 50];

Other examples

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let hasName: boolean = true;

let nothingMuch: null = null;
let nothing: undefined = undefined;

// built in objects
let now: Date = new Date();

// Array
let colors: string[] = ["red", "green", "blue"];
let myNumbers: number[] = [1, 2, 3, 4, 5];

// Classes
class Car {}
let car: Car = new Car();

// Object literal
let point: { x: number; y: number } = {
  x: 10,
  y: 20,
};

Type annotations for functions

1
2
3
4
// Function
const logNumber: (i: number) => void = (i: number) => {
  console.log(i);
};
  • In the above example, we have a function logNumber that takes in a parameter i of type number and returns void.
  • We can understand the above function by breaking it down into 3 parts as follows:
  1. const logNumber is a variable
  2. : (i: number) => void is the type annotation that tells TypeScript what type of value the variable logNumber will refer to.

    • void is the return type of the function. In this case, the function does not return anything.
  3. = (i: number) => { console.log(i); } is the value that the variable logNumber refers to.

    • (i: number) => { console.log(i); } is the function expression that takes in a parameter i of type number and returns void.

When to use type annotations?

  1. When we declare a variable on one line and initialize it later for example, we need to add a type annotation.
1
2
let age: number;
age = 30;

In the following example if we don’t add a type annotation, the TypeScript compiler will throw an error.

1
2
3
4
5
6
7
8
let words = ["red", "green", "blue"];
let foundWord;

for (let i = 0; i < words.length; i++) {
  if (words[i] === "green") {
    foundWord = true;
  }
}
  • In the above example, we have a variable foundWord that is declared on one line and initialized later. If we don’t add a type annotation, the TypeScript compiler will throw an error.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let words = ["red", "green", "blue"];
let foundWord: boolean;

for (let i = 0; i < words.length; i++) {
  if (words[i] === "green") {
    foundWord = true;
  }
}

// OR

let words = ["red", "green", "blue"];
let foundWord = false;

for (let i = 0; i < words.length; i++) {
  if (words[i] === "green") {
    foundWord = true;
  }
}
  1. When we want a variable to have a type that can’t be inferred correctly.
1
let names = ["John", "Jane", "Mary"];

In the following example, we have a variable with an array of numbers and another variable that is supposed to hold the largest number in the array.

1
2
3
4
5
6
7
8
let numbers = [-10, -1, 12];
let numberAboveZero = false;

for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] > 0) {
    numberAboveZero = numbers[i];
  }
}
  • In the above example, we have a variable numberAboveZero that is supposed to hold the largest number in the array. If we don’t add a type annotation, the TypeScript compiler will throw an error. To fix this, we can add a type annotation to the numberAboveZero variable.
1
2
3
4
5
6
7
8
let numbers = [-10, -1, 12];
let numberAboveZero: boolean | number = false;

for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] > 0) {
    numberAboveZero = numbers[i];
  }
}
  1. When a function returns the any type and we need to clarify the value.
1
2
3
const json = '{"x": 10, "y": 20}';
const coordinates = JSON.parse(json);
console.log(coordinates); // {x: 10, y: 20};
  • In the above example, the coordinates variable has a type of any. This is because the JSON.parse() function can return any type of value. We can add a type annotation to the coordinates variable to clarify the value. In the following code, we will examine how JSON.parse() works when passed different types of values.
1
2
3
4
5
6
JSON.parse("false"); // false
JSON.parse("4"); // 4 (number)
JSON.parse('{"x": 10, "y": 20}'); // {x: 10, y: 20} (object)
JSON.parse("John"); // "John" (string)
JSON.parse('{"value": 5}'); // {value: number} (object)
JSON.parse('{"name": "John"}'); // {name: string} (object)
  • In the above example, we can see that the JSON.parse() function can return different types of values. This is why the coordinates variable has a type of any. We can add a type annotation to the coordinates variable to clarify the value.
1
2
3
const json = '{"x": 10, "y": 20}';
const coordinates: { x: number; y: number } = JSON.parse(json);
console.log(coordinates); // {x: 10, y: 20};

What is Type inference?

This is a feature of TypeScript that allows us to omit type annotations. For example, we have a variable age that refers to a value of type number.

  • We don’t need to add a type annotation because TypeScript can infer the type of the value.
1
let age = 30; // Type inference

Some explanations on type inference

  • When we declare a variable and assign a value to it, we are performing a 2 step process.
    • first, we declare a variable (e.g. let age, or const hello). This is called variable declaration
    • then, we assign a value to the variable (e.g. age = 30, or hello = "hello"). This is called variable initialization
  • If we declare a variable and assign a value to it on the same line, type annotations are not needed because TypeScript will automatically infer the type of the value for us.
    • For example, we have a variable age that refers to a value of type number.
1
let age = 30; // Type inference
  • On the other hand, if we declare a variable and assign a value to it on different lines, we need to add a type annotation because TypeScript will not be able to infer the type of the value for us.
    • For example, we have a variable age that refers to a value of type number.
1
2
let age; // Type annotation
age = 30;
  • In the above example, we have a variable age that refers to a value of type number.
    • We need to add a type annotation because TypeScript will not be able to infer the type of the value for us.
1
2
let age: number; // Type annotation
age = 30;

When to use type inference?

  • It is recommended to ALWAYS use type inference when we can.

Type annotations and inference for functions

While we can use type annotations to describe the types of values that variables will refer to, we can also use them to describe the types of values that functions will return.

Type inference works out the return type for us, but we can also add a type annotation to explicitly specify the return type of the function. This is useful when the function returns the any type. For example, we have a function add that takes in two parameters a and b and returns the sum of the two parameters.

1
2
3
const add = (a: number, b: number): number => {
  return a + b;
};
  • We can understand the above code as follows:
    • const add - declare a variable add that refers to a value of type function
    • (a: number, b: number) - the function takes in two parameters a and b that are both of type number
    • : number - the function returns a value of type number
    • => - the function returns the sum of the two parameters
  • When we intend to not return anything from a function, we can use the void type annotation. For example, we have a function logNumber that takes in a parameter num and logs it to the console.
1
2
3
const logNumber = (num: number): void => {
  console.log(num);
};
  • We can use the never type annotation when we intend to never return anything from a function. For example, we have a function throwError that takes in a parameter message and throws an error with the message.
1
2
3
const throwError = (message: string): never => {
  throw new Error(message);
};

But we usually don’t need to use the never type annotation and can use the void type annotation instead. For example, we have a function throwError that takes in a parameter message and throws an error with the message.

1
2
3
4
5
const throwError = (message: string): void => {
  if (!message) {
    throw new Error(message);
  }
};
This post is licensed under CC BY 4.0 by the author.