Table of Contents
What is TypeScript
- TypeScript extends JavaScript by adding "types" to the language
- It provides a command line interface (cli) which we can run to compile our TypeScript to JavaScript via the cli command
tsc
- This helps spot bugs during development (i.e. when we're building our apps) and therefore prevent bugs from happening during "runtime" (when a user is using our apps)
For example, here's some TypeScript:
let firstName: string = "Sammy";
The : string
is TypeScript syntax which ensures that this variable will only ever contain a value of type string
If another developer attempts to assign a value to this variable of type number
, TypeScript will throw an error in VS Code and won't compile via the tsc
CLI
Setup a Local Environment
This section guides you through how to install and compile TypeScript code.
Create a folder to practice TypeScript
On your desktop create a typescript
directory:
- Open it in VS Code
- Open a terminal
Setup a package.json
file
npm init -y
Install TypeScript as a dev dependency
We use TypeScript during development (not to actually run the app), so we'll add the --save-dev
flag to indicate that it's a dependency for development
npm install typescript --save-dev
We should now be able to run the cli
npx tsc
Which should output something like Version 5.3.3
Create a tsconfig.json
file
We can control how the TypeScript compiler behaves via a JSON file, let's create it
npx tsc --init
This will generate a tsconfig.json
file with some default settings
Let's add a setting to tell the TypeScript compiler where to find our TypeScript files and where to send our compiled JavaScript files
{
"include": ["src"],
"compilerOptions": {
"outDir": "./build",
...
}
}
Now create an src/
directory and inside of it create a index.ts
Add
let firstName: string = "Sammy";
inside the index.ts
and run
npx tsc
You should now see a build
directory with an index.js
file inside.
Let's run
npx tsc --watch
Which will watch for changes in the TypesScript file and automatically transpile it to index.js
Vanilla TypeScript
Here are a few of the core features of TypeScript.
For a more detailed breakdown, take a look at the TypeScript Documentation.
Typing Variables
When creating variables, we can either provide an explicit type assignment:
let firstName: string = "Sammy";
In the code above, we're creating a variable and explicity telling TypeScript (via : string
) that it must be a string.
- There will be links to TS Playground for each section in this article going forward
- It compiles TypeScript to JavaScript and shows the compiled code on the right
- Click "Run" to execute the JavaScript and see any console logs
This makes your code easier to read/understand, and is more explicit.
You can also create a variable without a type and TypeScript will infer (i.e. guess) the type, based on the value:
let surname = "Abukmeil";
This is called an implicit type assignment.
You can hover over a variable in VS Code to check what type TypeScript inferred:
If you try to assign a value with the wrong type to a typed variable, TypeScript will highlight an error in VS Code (squiggly red line) and the TypeScript compiler will throw an error:
Type 'number' is not assignable to type 'string'
TS Playground - Incorrect Type Error
In TS Playground, you'll see the errors in two places:
- In the panel on the left by hovering over the red squiggly line
- In the panel on the right by switching to the "Errors" tab
This happens with both explicity typed variables and implicity typed variables.
Typing Arrays
When creating arrays, we can add a type like so
const games: string[] = ["Space Invaders", "Pac Man"];
The : string[]
above says "this array can only contain strings"
If we try to do the following
games.push(4);
We get the following error
Argument of type 'number' is not assignable to parameter of type 'string'
Tuples
A tuple is an array with a specific length & specific types for each index
let inventoryItem: [number, string, number] = [
1,
"Gaming Monitor",
100
];
The : [number, string, number]
above says "This array must have 3 items with the following types"
So if we were to re-assign this variable, this is allowed:
inventoryItem = [2, "Microwave", 40];
Whereas this isn't (since the last element in the array should be a number):
inventoryItem = [2, "Toaster", "35"];
We get the following error Type 'string' is not assignable to type 'number'
When creating tuples, you can also give a name to each array item for clarity
let inventoryItem: [id: number, name: string, price: number] = [
1,
"Gaming Monitor",
100,
];
Typing Objects
When creating objects, we can add a type like so
const game: { name: string; releaseYear: number } = {
name: "Resident Evil",
releaseYear: 1996,
};
where : { name: string; releaseYear: number }
specifies the name and type for each property
TS Playground Example - Typing an object
Enums
An enum is a group of constants (i.e. variables that should never change).
For example
enum Directions {
North,
East,
South,
West
}
In the code above, we've defined some directions that we'll use in our app.
let currentDirection = Directions.North;
In the code above, we're creating a variable and setting it equal to one of our enum values.
If we console.log()
this variable, we'll get 0
console.log(currentDirection); // 0
So each value in our enum is given a number starting from 0
.
Now that variable can only ever be assigned a value from this enum.
If we try to set the variable to some other value
currentDirection = "Something Incorrect";
We'll get the following error Type '"Something Incorrect"' is not assignable to type 'Directions'
We can also change the numbers that the enum values are linked to
enum StatusCodes {
Success = 200,
NotFound = 404,
ServerError = 500,
}
const currentStatus = StatusCodes.NotFound;
console.log(currentStatus); // 404
Or change the numbers that the enum values are linked to to be strings instead
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
const currentDirection = Direction.Up;
console.log(currentDirection); // UP
Type Aliases
We can define a Type Alias which makes types reusable and gives them a name.
type GameReleaseYear = number;
const pacmanYear: GameReleaseYear = 1980;
const spaceInvadersYear: GameReleaseYear = 1978;
TS Playground Example - Type Alias
In the code above, we created a Type Alias called GameReleaseYear
which is a number.
This Type Alias is used for multiple variables.
We can define a Type Alias for any type, including objects:
type Game = {
title: string;
year: number;
};
const game: Game = {
title: "Pac Man",
year: 1980
}
TS Playground Example - Type Alias for an object
Interfaces
An interface is similar to a type alias, however, you can only use them for objects
interface Game {
title: string;
year: number;
}
const game: Game = {
title: "Pac Man",
year: 1980,
};
TS Playground Example - Interface
Interfaces have an additional feature compared to Type Aliases, that they can extend other interfaces:
interface Game {
title: string;
year: number;
}
interface IndieGame extends Game {
developer: string
}
const indieGame: IndieGame = {
title: "Stardew Valley",
year: 2016,
developer: "ConcernedApe"
};
Union Types
A union type allows a value to be more than one type
function displayId(id: number | string) {
console.log(`ID is: ${id}`);
}
displayId(5);
displayId("ff72ce73-322d-4eb2-8029-c739d30c3ef5");
TS Playground Example - Union Type
In the code above, the id
property could either be a string
or a number
Typing Functions
We can specify what the return value type will be:
function getUserId(): number {
return 5;
}
The : number
above says "this function will return a value of type number"
We can add types for each of the parameters:
function sum(x: number, y: number): number {
return x + y;
}
TS Playground Example - Typing Functions
In the code above, we've added types for the parameters and the return type via : number
We can use a type alias to define the function types separately:
type Multiply = (x: number, y: number) => number;
const multiply: Multiply = (x, y) => {
return x * y;
}
Typing Classes
When creating classes, we can add types for the properties and the methods
class Car {
engineType: string;
constructor(engineType: string) {
this.engineType = engineType;
}
getEngineType(): string {
return this.engineType;
}
}
const car = new Car("Six cylinders");
In the code above, we specify that the class has a property called engineType
which is a string, and that the getEngineType()
method returns a string
We can also use private
and public
keywords to specify if certain properties / methods are accessible outside of the class
class Car {
private engineType: string;
constructor(engineType: string) {
this.engineType = engineType;
}
public getEngineType(): string {
return this.engineType;
}
}
const car = new Car("Six cylinders");
console.log(car.getEngineType());
console.log(car.engineType); // Error
In the code above, the engineType
property is set private
and therefore only accessible within the class, whereas the getEngineType()
method is public
and can be accessed outside of the class.
Instead of typing private engineType: string;
outside of the constructor and engineType: string
which is repetitive, we can combine both into the constructor:
class Car {
public constructor(private engineType: string) {
this.engineType = engineType;
}
public getEngineType(): string {
return this.engineType;
}
}
const car = new Car("Six cylinders");
TS Playground Example - Typing Classes
In the above code private engineType: string
defines a property and constructs it.
Generics
Generic Functions
Generics allow your typed functions to accept any type
function getValue<Type>(value: Type): Type {
return value;
}
In the code above, we define a generic function which can receive a parameter of any type, and return a value of any type.
const num = getValue<number>(10);
In the code above, we're using the generic function, giving a type of number
const str = getValue<string>("Hello");
TS Playground Example - Generic Function
In the code above, we're using the same generic function, giving a type of string
Here's another example
function reverse<Type>(array: Type[]): Type[] {
return array.reverse();
}
const reversedNumbers = reverse<number>([1, 2, 3]);
const reversedStrings = reverse<string>(["Hello", "Bye"]);
TS Playground Example - Generic Function Example 2
In the code above, the parameter array
is typed as Type[]
i.e. an array of whichever type was declared. The return value is also typed as Type[]
i.e. it will return an array of the declared type
Generic Type Aliases
When creating a type alias (See section "Type Aliases" above for a refresher), we can make it generic
type Person<Type> = {
name: string;
age: number;
job: Type;
};
In the code above, we've defined a generic type, where the job
property could be any type
const personOne: Person<boolean> = {
name: "Jane",
age: 21,
job: false,
};
In the code above, we're using our generic type alias and specifying that the job property will be a boolean
const personTwo: Person<string> = {
name: "John",
age: 35,
job: "Doctor",
};
TS Playground Example - Generic Type Alias
In the code above, we've used the same generic type alias, this time job will be a string
TypeScript With React
Here are a few way of using TypeScript in React
For a more detailed breakdown, take a look at the React Documentation.
Creating a Vite app
From this tutorial, we'll use Vite.
We can create a new Vite app with TypeScript by running the following command
npm create vite@latest
Choose the following options:
- Select a framework -> React
- Select a variant -> TypeScript
Note: Another common approach is to use Next JS
File Extension
Ensure your file extensions are .tsx
(it wont work with .js
or .jsx
)
Typing Props
Let's start with an example:
// App.tsx
function App() {
const handleTodo = (text: string) => {
console.log(text);
}
return (
<>
<NewTodo handleTodo={handleTodo} />
</>
);
}
// NewTodo.tsx
interface Props {
handleTodo: (text: string) => void;
}
function NewTodo({ handleTodo }: Props) {
return (
<>
<button onClick={() => handleTodo("Hello")}>Click</button>
</>
);
}
Here we've used an interface to define a type for the props object.
In this case, there's only one key value pair:
- Key:
handleTodo
- the name of the prop - Value:
(text: string) => void
- This prop is a function, so this type describes the function parameters and return type (void
means the function doesn't return anything)
Typing State
Let's start with an example:
interface Todo {
id: string;
text: string;
}
function App() {
const [todos, setTodos] = useState<Todo[]>([]);
return (
<>
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
<button
onClick={() => setTodos([...todos, { id: uuid(), text: "Hello" }])}
>
Add todo
</button>
</>
);
}
Here we've used an interface to define a type for the state variable.
Todo[]
means this state variable will contain an array of Todo
type objects
useState()
can be used as a Generic Function (as explained here) hence the following syntax useState<Todo[]>([])
TypeScript With Express
Here are a few way of using TypeScript in Express
Setting up an Express TypeScript app
Create an express app as usual:
npm init -y
npm i express
Install TypeScript as a dev dependancy, as well as some declaration packages for node and express
npm i -D typescript @types/express @types/node
These declaration packages (e.g.
@types/express
) can be found in the DefinitelyTyped GitHub Repo, which is a large collection of TypeScript type definitions.For example, here are the type definitions for Express JS: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/express
Running
npm i -D @types/express
brings these into yournode_modules/
directory and makes them usable in your application.
Create a tsconfig.json
file
npx tsc --init
and ensure you set a directory for your compiled JS files to be sent to
{
"compilerOptions": {
...
"outDir": "./dist"
...
}
}
This is how we'll run our app:
- In development -> we want node to run our
.ts
files - In production -> we want node to run our comipled
.js
Since node can't run .ts
files natively, we need to install a library for executing TypeScript via node called ts-node
npm i -D ts-node
And to watch for file changes in development, we'll use nodemon
npm i -D nodemon
And setup some scripts to run our app
{
"scripts": {
"build": "npx tsc",
"start": "node dist/index.js",
"dev": "nodemon src/index.ts"
}
}
Using Types in Express
Create a src
directory and a index.ts
file inside it with the following:
import express, { Express, Request, Response } from "express";
const app: Express = express();
app.get("/", (req: Request, res: Response) => {
res.send("Express + TypeScript Server");
});
app.listen(3000, () => {
console.log("Server is running on port 3000");
});
Here we're importing types for:
-
Express
-> Type for the return value fromexpress()
-
Request
-> Type for thereq
parameter -
Response
-> Type for theres
parameter
These are imported from the express
package, which will search from them in @types/express
Top comments (0)