TypeScript

Introduction

Introduction to TypeScript

What is TypeScript ?

Why do we need it ?

How is it different from JavaScript?

The 3 most asked questions about TypeScript

What is TypeScript?

  • TypeScript is a programming language developed by Microsoft.
  • It was created to fix some of JavaScript’s shortcoming

But What's wrong with JavaScript ? 🤨

JavaScript is… a little wild!

As we already know, JavaScript is a dynamic language where we can do all kinds of crazy things, like:

  • Reference variables that don’t exist.
  • Work with objects of unknown shape.
  • Pass random data types to functions.

console.log(hello); // ❌ ReferenceError: hello is not defined

function add(a, b) {
  return a + b;
}

console.log(add(5, "5")); // 🤯 "55" (String concatenation!)

And that's the problem...

The Real Problem

JavaScript lets you do anything… but at what cost?

  • JavaScript doesn’t check for errors before running the code

  • The browser interprets your code at runtime

  • If there’s a bug, you won’t know until the error actually happens

But what if we could prevent errors before running the code? 😏

What is TypeScript? (technically)

  • TypeScript is a strongly typed, object-oriented, and compiled programming language. It is a superset of JavaScript.

 

  • First released in 2012 and has since become one of the most popular tools for building scalable and maintainable web applications.

But what does "superset" mean?

Superset ? 🤔

TypeScript is built on top of JavaScript so any valid JavaScript code is also valid TypeScript code.

whether it is on the frontend (client)

or backend (server).

 

So we can say that : 

Typescript extends Javascript

Why do we need it ?

  • Static Typing: Catches errors at compile time instead of runtime.
  • Improved IDE Support: Better autocompletion, navigation, and refactoring.
  • Large Ecosystem: Integrates seamlessly with JavaScript libraries.

The most important feature that ts offers is Static typing

In short

Typescript

Jasvascript with type checking !

TypeScript configuration

Setting Up the Development Environment

1. Install Node.js and npm

node -v
npm -v

2. Verify the installation

Setting Up A TypeScript

project

1. Create a new directory

cd ts-test

2. Navigate to it

3. Initialize a new Node.js project

npm init -y

6. Create a tsconfig.json file

npx tsc --init
mkdir ts-test

This file defines how TypeScript should compile your code (e.g., target JavaScript version, module system, etc.).

5. Create index.ts file

4. Install Typescript

npm i typescript

Become a TypeScript

developer

1. Write your first code

npx tsc index.ts

2. Compile using TSC compiler 

3. Try something else !

let name: string = "Fatima"

console.log(`Hello ! My name is ${name} and i'm your instructor ^^`);

name = 24
console.log(name);
console.log('Hello world !')

Et Voila ! you're officially a TS developer✨

TS Compiler

configuration

How to configure TS

  • tsconfig.json is a configuration file for TypeScript projects.

  • It specifies the root files and the compiler options required to compile the project.

  • Basic Structure of 'tsconfig.json'
{
  "compilerOptions": {
    // Compiler options go here
  },
  "include": [
    // List of files to include
  ],
  "exclude": [
    // List of files to exclude
  ]
}

 Key Compiler Options

  • module: Specifies the module system t for the compiled code.
"module": "commonjs"
  • strict: Enables all strict type-checking options
"strict": true
  • jsx: Specifies how JSX should be compiled.
"jsx": "react"
  • target: Specifies the target JS version for the compiled output.
"target": "es6"

Important Compiler Options

  • moduleResolution: Determines how modules are resolved.
"moduleResolution": "node"
  • esModuleInterop: Enables compatibility with CommonJS modules.
"esModuleInterop": true
  • skipLibCheck: Skips type checking of declaration files (.d.ts).
"skipLibCheck": true
  • forceConsistentCasingInFileNames: Prevents case-sensitive import mismatches.
     
"forceConsistentCasingInFileNames": true

Important Compiler Options

  • rootDir: Specifies the source directory containing TypeScript files.

"rootDir": "./src"
  • outDir: Defines the output directory for compiled .js files.

"outDir": "./dist"
  • noEmitOnError: Prevents TypeScript from generating .js files if there are compilation errors.

"noEmitOnError": true

Example tsconfig.json

{
  "compilerOptions": {
    "target": "es6", // Compile to ES6 JavaScript
    "module": "commonjs", // Use CommonJS modules
    "strict": true, // Enable all strict type-checking options
    "jsx": "react", // Support React JSX
    "moduleResolution": "node", // Use Node.js-style module resolution
    "esModuleInterop": true, // Enable ES module interoperability
    "skipLibCheck": true, // Skip type-checking of declaration files
    "forceConsistentCasingInFileNames": true, // Ensure consistent file casing
    "outDir": "./dist", // Output directory for compiled files
    "rootDir": "./src" // Root directory for source files
  },
  "include": ["src/**/*"], // Include all files in the src directory
  "exclude": ["node_modules", "dist"] // Exclude node_modules and dist directories
}

How to go from JS to TS

Basic TypeScript Syntax

Types and Type Annotations

Javascript

  • number
  • string
  • boolean
  • null
  • undefined
  • object

Typescript

  • any
  • unknown
  • never
  • enum
  • tuple
  • generics
let age: number = 30;
let name: string = "Fatima";
let isStudent: boolean = false;

The any Type

The any type in TypeScript allows a variable to hold values of any type. It effectively disables TypeScript’s type checking, making it similar to regular JavaScript.

let data: any = "Hello";
data = 42; // No error, but could lead to bugs
function render(document: any) {
  console.log(document)
}

Here, data can be reassigned to any value without restrictions.

Using any removes TypeScript's benefits, such as type safety and autocompletion. It can lead to runtime errors and make debugging harder.

The unknown Type

The unknown type is a safer alternative to any, as it restricts how the value can be used. It represents a value that is not known at compile time.

let data: unknown;

data = "Hello";  
data = 42;  
data = true;  

As a best practice, avoid unknown unless absolutely necessary, and prefer explicitly typed variables for better type safety! 

Advanced TypeScript Features

Functions in TypeScript

TypeScript allows us to define function parameters and return types to ensure type safety and catch errors early.

function greet(name: string, age?: number): string {
  return age ? `Hello, ${name}, you are ${age} years old.` : `Hello, ${name}!`;
}

console.log(greet("Alice")); // ✅ "Hello, Alice!"
console.log(greet("Bob", 25)); // ✅ "Hello, Bob, you are 25 years old."

Enums in TypeScript

Enums (Enumerations) in TypeScript allow you to define a set of named constants

enum Status {
  Pending,  // 0
  InProgress, // 1
  Completed // 2
}

let taskStatus: Status = Status.InProgress;
console.log(taskStatus); // Output: 1
enum Role {
  User = 1,
  Admin = 5,
  SuperAdmin = 10
}

console.log(Role.Admin); // Output: 5

Tuples in TypeScript

A tuple in TypeScript is a fixed-length array where each element has a specific type. Unlike regular arrays, tuples enforce strict type positioning.

let user: [string, number] = ["Alice", 25];  
  • Enforces a fixed structure with specific types.
  • Useful for returning multiple values with known positions (e.g., [status, message]).

Objects in TypeScript

In TypeScript, objects can have strict type definitions to ensure their structure remains consistent.

let user: {id: number; name: string; email: string } = {  
  id: 1,
  name: "Fatima",  
  age: "test@gmail.com"  
};

Interfaces and Type Aliases

interface User {
  id: number;
  name: string;
  email: string;
}
type Employee = {
  name: string;
  position: string;
};
type ID = number | string;

So far, we've seen how TypeScript allows us to define object types explicitly. However, as our code grows, repeating object structures can become inefficient and hard to maintain

When to Use Type vs Interface

Type

type ID = string | number;
type Coordinates = [number, number];
type Status = "active" | "inactive";
  • Use for creating primive types.
  • Use for creating union types.
  • Use for creating tuples.
  • Use for creating intersections.

When to Use Type vs Interface

Interface

type ID = string | number;
type Coordinates = [number, number];
type Status = "active" | "inactive";
interface Animal {
  name: string;
}
interface Dog extends Animal {
  breed: string;
}
  • Use for defining object structures (shapes).
  • Preferred for extending (inheritance).

Exercice

const product = {
  name: "Shampoo",
  price: 2.99,
  images: ["image-1.png", "image-2.png"],
  status: "published",
};
  • create a type describing this object
  • use an enum for the "status" field

From Objects to Generics

So far, we've defined types explicitly for functions, objects, and arrays. But what if we need a flexible way to handle multiple types while maintaining type safety?

 

This is where Generics come in ✨

Generics

  • Generics allow us to write reusable and type-safe code without specifying exact types upfront.
  • They enable us to create components, functions, and data structures that work with different types dynamically.
  • Why use generics

  1. Avoid code duplication by making functions & components flexible.
  2. Maintain type safety without sacrificing reusability.
  3. Work with any data type while still enforcing constraints.

Generic Functions

Without Generics (Bad Practice)

function identity(value: any): any {
  return value;
}

const result = identity(42); // No type safety 😬

With Generics (Best Practice)

function identity<T>(value: T): T {
  return value;
}

const num = identity(42);      // num is inferred as number
const text = identity("Hello"); // text is inferred as string

Problem: We lose type safety because any can be anything.

  • <T> represents a generic type.
  • TypeScript infers the type automatically!

Generic Constraints

Sometimes, we want to restrict the types that can be used.

interface HasLength {
  length: number;
}
function logLength<T extends HasLength>(value: T): void {
  console.log(`Length: ${value.length}`);
}
logLength("Hello"); // ✅ Works (string has length)
logLength([1, 2, 3]); // ✅ Works (array has length)
// logLength(42); ❌ Error (number has no length)

T extends HasLength restricts T to only types with a .length property.

Utility Types

Partial<T>

interface User {
  id: number;
  name: string;
  age: number;
}

type PartialUser = Partial<User>;
// PartialUser is { id?: number; name?: string; age?: number; }

Omit<T, K>

type UserWithoutEmail = Omit<User, 'email'>;
// UserWithoutAge is { id: number; name: string; }

Pick<T, K>

type UserName = Pick<User, 'name'>;
// UserName is { name: string; }
  • Makes all properties in a type optional.
  • Removes specific properties from a type.
  • Picks specific properties from a type.

TypeScript with React

Setting Up a TypeScript + Vite React Project

Installing Vite with TypeScript

npm create vite@latest 

Navigate to the project and install dependencies

cd my-react-app
npm install

Vite automatically generates a tsconfig.json file.

Typing Props

interface UserProps {
  name: string;
  age: number;
  isActive?: boolean; // Optional prop
}

const User = ({ name, age, isActive = true }: UserProps) => {
  return (
    <div>
      <p>Name: {name}</p>
      <p>Age: {age}</p>
      <p>Status: {isActive ? "Active" : "Inactive"}</p>
    </div>
  );
};
type ButtonProps = {
  label: string;
  onClick: () => void;
};

const Button: React.FC<ButtonProps> = ({ label, onClick }) => {
  return <button onClick={onClick}>{label}</button>;
};

Typing Hooks

useState

const [count, setCount] = React.useState<number>(0);
const [user, setUser] = React.useState<User | null>(null);

useReducer

type Action = { type: "INCREMENT" } | { type: "DECREMENT" };
const counterReducer = (state: number, action: Action): number => {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
};
  • explicitly defining the state type helps prevent errors
  • Reducers benefit from strong TS typing

useEffect

  •  doesn’t require explicit typing

Typing Events

Input Events

const InputField: React.FC = () => {
  const [value, setValue] = useState<string>("");
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };
  return <input type="text" value={value} onChange={handleChange} />;
};

Click Events

const Button: React.FC = () => {
  const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
    console.log("Button clicked!");
  };
  return <button onClick={handleClick}>Click Me</button>;
};

Typing API Calls (Fetching Data)

Typing API Responses

type User = {
  id: number;
  name: string;
  email: string;
};
const fetchUsers = async (): Promise<User[]> => {
  const response = await fetch("https://jsonplaceholder.typicode.com/users");
  return response.json();
};

Using TypeScript with API calls ensures correct data handling

Copy of TypeScript introduction

By Fatima BENAZZOU

Copy of TypeScript introduction

  • 107