TypeScript Basics

Last Modified: 04/26/2023 08:49AM GMT+0

Contents #

What is TypeScript? #

TypeScript is a statically typed superset of JavaScript.

It allows us to explicitly define what each of our objects are and provides us with information of what data we're working with during development.

Declaring Types #

In order to declare types for variables in TypeScript we use the following syntax:

let name: string = 'Lexie'

Here we assign the variable name to a string type. If we reassign this to a value of a different type, say the number 5, TypeScript will show an error:

name = 5 // Error, `number` is not a valid `string` type.

Type Primitives #

let isDog: boolean = true     // `true` or `false` values
let age: number = 2 // `integer` or `floating` values
let name: string = 'Lexie' // string values (🤷)
let nothing: void = undefined // undefined value

Arrays #

There are two syntaxes to define arrays of things in TypeScript:

let fib: number[] = [1, 2, 3, 5, 8]
let names: string[] = ['Lexie', 'Daisy', 'Mohgwyn']

// OR

let fib: Array<number> = [1, 2, 3, 5, 8]
let names: Array<string> = ['Lexie', 'Daisy', 'Mohgwyn']

Objects #

The syntax for defining object types is as follows:

{ propOne: typeOfPropOne, propTwo: typeOfPropTwo, ..., propN: typeOfPropN }

For example:

let dog: { name: string, age: number } = {
name: 'Lexie',
age: 2,
}

We can also define optional properties like so:

let dog: { name: string, age: number, breed?: string } = {
name: 'Lexie',
age: 2,
}

Here the breed property is an optional property meaning we don't have to assign it a value, but if we do, it must be a string type.

Literal Types #

In TypeScript, we can use concrete values to assign objects with.

For example:

let name: 'Lexie'       // `name` is defined with a literal string type `'Lexie'`
name = 'Lexie' // Okay: 'Lexie' === 'Lexie'
name = 'Something else' // Not okay: 'Lexie' !== 'Something else'

let one: 1
one = 1 // Okay
one = 2 // Not okay.

The any type #

The any type is a type that accepts any value. Basically removing type checking for an object.

let foo: any;

foo = true
foo = 3.14
foo = 'Hello'
foo = [1, 2, 4, 8, 16]
foo = { quas: 1, wex: 2, exort: 3 }

// etc.

Type Inferrence #

We can omit defining types explicitly and still have TypeScript assign a type to an object due to type inferrence: meaning TypeScript will make a (very good) guess of what type an object is based on certain information.

For example:

let name = 'Lexie'
name = 'Daisy' // Okay.
name = 3.14 // Error, `number` is not a valid `string` type.

Here, even though the type wasn't defined for the name variable, TypeScript still assigned it with a string type because of the value that the variable was initialized to.

So when name was assigned with the value 'Daisy', TypeScript didn't show errors because 'Daisy' is a valid string type.

When name was assigned with the value 3.14 however, TypeScript showed an error because name was assigned a number value.

Typing Functions #

To declare a type for a function, we use the ff. syntax:

function functionName(argOne: typeOfArgOne, argTwo: typeOfArgTwo, ..., argN: typeOfArgN): returnType {
// body of function goes here.
}

For arrow functions:

let functionName = (argOne: typeOfArgOne, argTwo: typeOfArgTwo, ..., argN: typeOfArgN): returnType => {
// body of function goes here.
}

For example:

function greet(name: string): void {
console.log(`Hello, ${name}`)
}

let add = (a: number, b: number): number => a + b

We can omit the returnType and have TypeScript infer its type instead:

let add = (a: number, b: number) => a + b    // Returns a `number` type
let concat = (a: string, b: number) => a + b // Returns a `string` type

Type Operators #

In TypeScript there are two type operators that we can use to compose types with, these are namely:

Union Operator #

An operator that assigns exactly one(1) type that is matched in a set of two or more types.

The syntax for this operator is as follows:

typeOne | typeTwo | ... | typeN

For example:

let nameOrAge: string | number;

nameOrAge = 'Lexie' // Okay.
nameOrAge = 2 // Also okay.
nameOrAge = true // Error, `boolean` is not a valid `string` or `number` type.

Intersection Operator #

An operator that combines properties of two or more types.

The syntax for this operator is as follows:

typeOne & typeTwo & ... & typeN

For example:

let dog: { name: string, age: number } & { breed: string, gender?: string } = {
name: 'Lexie',
age: 2,
breed: 'Shih Tzu',
}

Type Aliases #

We can define new types using Type Aliases.

The syntax for this is as follows:

type NameOfThisType = theType

For example:

type NameOrAge = string | number
let nameOrAge: NameOrAge;

nameOrAge = 'Lexie'
nameOrAge = 2
nameOrAge = false // Error, `boolean` is not a valid `NameOrAge` type

type Color = 'red' | 'green' | 'blue'
let myColor: Color;

myColor = 'red'
myColor = 'green'
myColor = 'blue'
myColor = 'magenta' // Error, 'magenta' is not a valid `Color` type

type Dog = {
name: string,
age: number,
breed: string,
}

let dog: Dog = {
name: 'Lexie',
age: 2,
breed: 'Shih Tzu',
furColors: ['black', 'white', 'brown'], // Error, `furColors` is not in type `Dog`
}

Type Interfaces #

Similar to a Type Alias, a Type Interface allows us to define new types. But unlike a Type Alias, a Type Interface can only define types with a set of properties.

The syntax for this is as follows:

interface MyInterface {
propOne: typeOfPropOne
propTwo: typeOfPropTwo
...
propN: typeOfPropN
}

For example:

interface Dog {
name: string,
age: number,
breed: string,
}

let dog: Dog = {
name: 'Lexie',
age: 2,
breed: 'Shih Tzu',
furColors: ['black', 'white, 'brown'], // Error, `furColors` is not in type `Dog`
}

NOTE: There are a few more differences to aliases and interfaces. If you want to learn more about this please read TypeScript's Official Docs regarding Aliases vs. Types

Generics #

Say we have a function like the Identity Function which simply returns whatever input we provide to it:

let identity = (arg) => arg

How should we type this?

If, for example, we provide it with a type like number.

let identity = (arg: number): number => arg

Then this will only account for the number values; neglecting all other value types such as strings, booleans, arrays, etc.

If we type it with the any type however, then we can match all the types.

let identity = (arg: any): any => arg

When we do this though, we add back ambiguity to our code: giving rise to questions such as When we input it a number value, what will it give us?

To mitigate this problem, we can use Generic Types.

Generic Types #

TypeScript provides the Generic type: a type that allows us to define the type of an object based on how we use it.

The syntax for this for a function is as follows:

function myFunction<NameOfGeneric>(argOne: typeOfArgOne, ..., argN: typeOfArgN): returnType {
// body of the function goes here
}

For arrow functions:

let myFunction = <NameOfGeneric,>(argOne: typeOfArgOne, ..., argN: typeOfArgN): returnType => {
// body of the function goes here
}

NOTE the , after the Generic name declaration.

The Generic type can be assigned to arguments, the return type, or to any objects within the function.

Using this on the identity function example:

let identity = <T,>(arg: T): T => arg

Now, when we use the identity function we must define what type the Generic type T is, like so:

// Okay.
identity<boolean>(false)
identity<number>(5)
identity<string>('Hello')
identity<number[]>([1, 2, 3])
identity<{ name: string }>({ name: 'Lexie' })

// Not okay.
identity<boolean>('Hello') // Error, `string` is not a valid `boolean` type

More Resources #

If you want to learn more about how to use TypeScript and all of its other features, please take a look at the Official TypeScript Docs

Thanks for reading.