What is TypeScript ?
Why do we need it ?
How is it different from JavaScript?
The 3 most asked questions about TypeScript
As we already know, JavaScript is a dynamic language where we can do all kinds of crazy things, like:
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!)
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
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
The most important feature that ts offers is Static typing
1. Install Node.js and npm
node -v
npm -v
2. Verify the installation
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
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✨
tsconfig.json is a configuration file for TypeScript projects.
It specifies the root files and the compiler options required to compile the project.
tsconfig.json'
{
"compilerOptions": {
// Compiler options go here
},
"include": [
// List of files to include
],
"exclude": [
// List of files to exclude
]
}
"module": "commonjs"
"strict": true
"jsx": "react"
"target": "es6"
"moduleResolution": "node"
"esModuleInterop": true
(.d.ts)
."skipLibCheck": true
"forceConsistentCasingInFileNames": true
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
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
}
let age: number = 30;
let name: string = "Fatima";
let isStudent: boolean = false;
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 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!
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 (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
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];
[status, message]
).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"
};
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
type ID = string | number;
type Coordinates = [number, number];
type Status = "active" | "inactive";
type ID = string | number;
type Coordinates = [number, number];
type Status = "active" | "inactive";
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
const product = {
name: "Shampoo",
price: 2.99,
images: ["image-1.png", "image-2.png"],
status: "published",
};
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 ✨
Why use generics
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.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.
interface User {
id: number;
name: string;
age: number;
}
type PartialUser = Partial<User>;
// PartialUser is { id?: number; name?: string; age?: number; }
type UserWithoutEmail = Omit<User, 'email'>;
// UserWithoutAge is { id: number; name: string; }
type UserName = Pick<User, 'name'>;
// UserName is { name: string; }
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.
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>;
};
const [count, setCount] = React.useState<number>(0);
const [user, setUser] = React.useState<User | null>(null);
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;
}
};
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} />;
};
const Button: React.FC = () => {
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
console.log("Button clicked!");
};
return <button onClick={handleClick}>Click Me</button>;
};
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