Blog Details
MD ZAKIR HOSSAIN BHUIYAN
10 Oct 2024
8 min read
TypeScript has quickly become a favourite among developers because it adds static type-checking to JavaScript, which helps catch errors early. As a beginner, you may already be comfortable with basic types and interfaces, but learning more advanced TypeScript features will make your code cleaner, more flexible, and easier to maintain.
In this beginner-friendly blog, we’ll break down some of the more advanced TypeScript concepts, explain them clearly, and provide code examples to help you understand how they work.
Generics are a powerful feature in TypeScript that lets you write functions or components that work with any type. Instead of hardcoding a specific type, generics use placeholders so that the function can adapt to different data types while keeping type safety.
Example: Generic Functions
Here’s a simple function that returns whatever value you pass to it, but it works with any type!
function identity<T>(value: T): T {
return value;
}
// Usage
const numberValue = identity(42); // T is a number
const stringValue = identity("Hello"); // T is a string
T
is a placeholder for a type.identity
works with any type, whether a number, string, or something else.
Why is this useful?
Generics let you write flexible and reusable code. You can apply the same logic to multiple types without writing separate functions for each.
Conditional types allow you to create types based on conditions, much like an if statement in regular code, but for types.
Example: Basic Conditional Type
type IsString<T> = T extends string ? "Yes" : "No";
// Usage
type Check1 = IsString<string>; // "Yes"
type Check2 = IsString<number>; // "No"
Here, the IsString type checks if T is a string. If it is, it returns "Yes", otherwise it returns "No".
Why is this useful?
Conditional types are handy when you need to create different types based on some condition, making your types smarter and more adaptable.
Mapped types allow you to take an existing type and transform its properties. This is useful when you need to modify types across an entire object.
Example: Making All Properties Optional
type Optional<T> = {
[K in keyof T]?: T[K];
};
interface User {
id: number;
name: string;
email: string;
}
type OptionalUser = Optional<User>;
// OptionalUser is now:
{
id?: number;
name?: string;
email?: string;
}
Here, Optional<T> loops over all properties in T and makes them optional.
Why is this useful?
Mapped types make it easy to apply changes across an entire type, like making all properties optional, required, or read-only.
TypeScript provides several built-in utility types that simplify common type transformations. These are pre-built helpers that can save you time when working with types.
Example: Pick and Omit
Pick
lets you select specific properties from a type.Omit
allows you to remove properties from a type.
interface Person {
id: number;
name: string;
age: number;
email: string;
}
// Pick: Select only "name" and "age" from Person
type PersonPreview = Pick<Person, "name" | "age">;
// Omit: Remove "email" from Person
type PersonWithoutEmail = Omit<Person, "email">;
Why is this useful?
Utility types reduce the amount of boilerplate code you need to write. Instead of manually creating new types, you can quickly transform existing ones.
Type guards are functions that let you check the type of a variable at runtime. This is useful when working with union types, where a variable can be of more than one type.
Example: Using typeof
function isString(value: any): value is string {
return typeof value === "string";
}
function printValue(value: string | number) {
if (isString(value)) {
console.log("It's a string: " + value.toUpperCase());
} else {
console.log("It's a number: " + value.toFixed(2));
}
}
isString
is a type guard that checks if a value is a string.if
block, TypeScript knows whether value
is a string or a number, so you can safely call string or number methods.
Why is this useful?
Type guards help TypeScript narrow down the type of a variable, ensuring that you call the right methods without errors.
Interfaces and classes allow you to create custom types and object-oriented structures. Interfaces define the shape of an object, while classes provide a blueprint for creating objects with specific properties and methods.
Example: Using Interfaces
interface Animal {
name: string;
sound: () => void;
}
const dog: Animal = {
name: "Dog",
sound: () => console.log("Woof!")
};
dog.sound(); // Output: Woof!
Example: Using Classes
class Car {
constructor(public make: string, public model: string) {}
getCarInfo() {
return `${this.make} ${this.model}`;
}
}
const myCar = new Car("Toyota", "Corolla");
console.log(myCar.getCarInfo()); // Output: Toyota Corolla
Why is this useful?
Using interfaces and classes helps you define clear and structured data models, making your code more organized and maintainable.
By understanding and applying these concepts, you’ll become more proficient in TypeScript and able to handle complex projects with ease.
As you continue to use TypeScript, mastering advanced concepts like Generics, Conditional Types, Mapped Types, Utility Types, and Type Guards will help you write more flexible, maintainable, and robust code. These concepts may seem tricky at first, but with practice, they will become essential tools in your development toolkit.
Don’t be afraid to experiment with these features in your own projects, and see how they can make your code smarter and more powerful!
Happy coding!
Don’t worry, we don’t spam!