Home > Web Front-end > JS Tutorial > body text

Top Advanced typescript concepts that Every Developer Should Know

DDD
Release: 2024-11-26 07:18:10
Original
865 people have browsed it

TypeScript is a modern programming language often preferred over JavaScript for its added type safety. In this article, I'll share the top 10 TypeScript concepts that will help sharpen your TypeScript programming skills.Are you ready?Let's go.

Top Advanced typescript concepts that Every Developer Should Know

1.Generics : Using generics we can create reusable types, which will be helpful in dealing with the data of the today as well as the data of the tomorrow.
Example of Generics:
We might want a function in Typescript that takes an argument as some type, and we might want to return the same type.

function func<T>(args:T):T{
    return args;
}
Copy after login
Copy after login

2.Generics with Type Constraints : Now let's limit the type T by defining it to accept only strings and integers:

function func<T extends string | number>(value: T): T {
    return value;
}

const stringValue = func("Hello"); // Works, T is string
const numberValue = func(42);      // Works, T is number

// const booleanValue = func(true); // Error: Type 'boolean' is not assignable to type 'string | number'
Copy after login
Copy after login

3.Generic Interfaces:
Interface generics are useful when you want to define contracts (shapes) for objects, classes, or functions that work with a variety of types. They allow you to define a blueprint that can adapt to different data types while keeping the structure consistent.

// Generic interface with type parameters T and U
interface Repository<T, U> {
    items: T[];           // Array of items of type T
    add(item: T): void;   // Function to add an item of type T
    getById(id: U): T | undefined; // Function to get an item by ID of type U
}

// Implementing the Repository interface for a User entity
interface User {
    id: number;
    name: string;
}

class UserRepository implements Repository<User, number> {
    items: User[] = [];

    add(item: User): void {
        this.items.push(item);
    }

     getById(idOrName: number | string): User | undefined {
        if (typeof idOrName === 'string') {
            // Search by name if idOrName is a string
            console.log('Searching by name:', idOrName);
            return this.items.find(user => user.name === idOrName);
        } else if (typeof idOrName === 'number') {
            // Search by id if idOrName is a number
            console.log('Searching by id:', idOrName);
            return this.items.find(user => user.id === idOrName);
        }
        return undefined; // Return undefined if no match found
    }
}

// Usage
const userRepo = new UserRepository();
userRepo.add({ id: 1, name: "Alice" });
userRepo.add({ id: 2, name: "Bob" });

const user1 = userRepo.getById(1);
const user2 = userRepo.getById("Bob");
console.log(user1); // Output: { id: 1, name: "Alice" }
console.log(user2); // Output: { id: 2, name: "Bob" }

Copy after login
Copy after login

4.Generic Classes:: Use this when you want all the properties in your class to adhere to the type specified by the generic parameter. This allows for flexibility while ensuring that every property of the class matches the type passed to the class.

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

class UserDetails<T extends User> {
    id: T['id'];
    name: T['name'];
    age: T['age'];

    constructor(user: T) {
        this.id = user.id;
        this.name = user.name;
        this.age = user.age;
    }

    // Method to get user details
    getUserDetails(): string {
        return `User: ${this.name}, ID: ${this.id}, Age: ${this.age}`;
    }

    // Method to update user name
    updateName(newName: string): void {
        this.name = newName;
    }

    // Method to update user age
    updateAge(newAge: number): void {
        this.age = newAge;
    }
}

// Using the UserDetails class with a User type
const user: User = { id: 1, name: "Alice", age: 30 };
const userDetails = new UserDetails(user);

console.log(userDetails.getUserDetails());  // Output: "User: Alice, ID: 1, Age: 30"

// Updating user details
userDetails.updateName("Bob");
userDetails.updateAge(35);

console.log(userDetails.getUserDetails());  // Output: "User: Bob, ID: 1, Age: 35"
console.log(new UserDetails("30"));  // Error: "This will throw error" 
Copy after login
Copy after login

5.Constraining Type Parameters to Passed Types: At times, we want to a parameter type to depend on some other passed parameters.Sounds confusing,let's see the example below.

function getProperty<Type>(obj: Type, key: keyof Type) {
  return obj[key];
}

let x = { a: 1, b: 2, c: 3 };
getProperty(x, "a");  // Valid
getProperty(x, "d");  // Error: Argument of type '"d"' is not assignable to parameter of type '"a" | "b" | "c"'.

Copy after login
Copy after login

6.Conditional Types : Often, we want our types to be either one type or another. In such situations, we use conditional types.
A Simple example would be:

function func(param:number|boolean){
return param;
}
console.log(func(2)) //Output: 2 will be printed
console.log(func("True")) //Error: boolean cannot be passed as argument

Copy after login
Copy after login

A little bit complex example:

type HasProperty<T, K extends keyof T> = K extends "age" ? "Has Age" : "Has Name";

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

let test1: HasProperty<User, "age">;  // "Has Age"
let test2: HasProperty<User, "name">; // "Has Name"
let test3: HasProperty<User, "email">; // Error: Type '"email"' is not assignable to parameter of type '"age" | "name"'.

Copy after login

6.Intersection Types: These types are useful when we want to combine multiple types into one, allowing a particular type to inherit properties and behaviors from various other types.
Let's see an interesting example for this:

// Defining the types for each area of well-being

interface MentalWellness {
  mindfulnessPractice: boolean;
  stressLevel: number; // Scale of 1 to 10
}

interface PhysicalWellness {
  exerciseFrequency: string; // e.g., "daily", "weekly"
  sleepDuration: number; // in hours
}

interface Productivity {
  tasksCompleted: number;
  focusLevel: number; // Scale of 1 to 10
}

// Combining all three areas into a single type using intersection types
type HealthyBody = MentalWellness & PhysicalWellness & Productivity;

// Example of a person with a balanced healthy body
const person: HealthyBody = {
  mindfulnessPractice: true,
  stressLevel: 4,
  exerciseFrequency: "daily",
  sleepDuration: 7,
  tasksCompleted: 15,
  focusLevel: 8
};

// Displaying the information
console.log(person);

Copy after login

7.infer keyword: The infer keyword is useful when we want to conditionally determine a specific type, and when the condition is met, it allows us to extract subtypes from that type.
This is the general syntax:

type ConditionalType<T> = T extends SomeType ? InferredType : OtherType;
Copy after login

Example for this:

type ReturnTypeOfPromise<T> = T extends Promise<infer U> ? U : number;

type Result = ReturnTypeOfPromise<Promise<string>>;  // Result is 'string'
type ErrorResult = ReturnTypeOfPromise<number>;      // ErrorResult is 'never'

const result: Result = "Hello";
console.log(typeof result); // Output: 'string'
Copy after login

8.Type Variance : This concept talks how subtype and supertype are related to each other.
These are of two types:
Covariance: A subtype can be used where a supertype is expected.
Let's see an example for this:

function func<T>(args:T):T{
    return args;
}
Copy after login
Copy after login

In the above example, Car has inherited properties from Vehicle class,so it's absolutely valid to assign it to subtype where supertype is expected as subtype would be having all the properties that a supertype has.
Contravariance: This is opposite of covariance.We use supertypes in places where subType is expected to be.

function func<T extends string | number>(value: T): T {
    return value;
}

const stringValue = func("Hello"); // Works, T is string
const numberValue = func(42);      // Works, T is number

// const booleanValue = func(true); // Error: Type 'boolean' is not assignable to type 'string | number'
Copy after login
Copy after login

When using contravariance, we need to be cautious not to access properties or methods that are specific to the subtype, as this may result in an error.

9. Reflections: This concept involves determining the type of a variable at runtime. While TypeScript primarily focuses on type checking at compile time, we can still leverage TypeScript operators to inspect types during runtime.
typeof operator : We can make use of typeof operator to find the type of variable at the runtime

// Generic interface with type parameters T and U
interface Repository<T, U> {
    items: T[];           // Array of items of type T
    add(item: T): void;   // Function to add an item of type T
    getById(id: U): T | undefined; // Function to get an item by ID of type U
}

// Implementing the Repository interface for a User entity
interface User {
    id: number;
    name: string;
}

class UserRepository implements Repository<User, number> {
    items: User[] = [];

    add(item: User): void {
        this.items.push(item);
    }

     getById(idOrName: number | string): User | undefined {
        if (typeof idOrName === 'string') {
            // Search by name if idOrName is a string
            console.log('Searching by name:', idOrName);
            return this.items.find(user => user.name === idOrName);
        } else if (typeof idOrName === 'number') {
            // Search by id if idOrName is a number
            console.log('Searching by id:', idOrName);
            return this.items.find(user => user.id === idOrName);
        }
        return undefined; // Return undefined if no match found
    }
}

// Usage
const userRepo = new UserRepository();
userRepo.add({ id: 1, name: "Alice" });
userRepo.add({ id: 2, name: "Bob" });

const user1 = userRepo.getById(1);
const user2 = userRepo.getById("Bob");
console.log(user1); // Output: { id: 1, name: "Alice" }
console.log(user2); // Output: { id: 2, name: "Bob" }

Copy after login
Copy after login

instanceof Operator: The instanceof operator can be used to check if an object is an instance of a class or a particular type.

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

class UserDetails<T extends User> {
    id: T['id'];
    name: T['name'];
    age: T['age'];

    constructor(user: T) {
        this.id = user.id;
        this.name = user.name;
        this.age = user.age;
    }

    // Method to get user details
    getUserDetails(): string {
        return `User: ${this.name}, ID: ${this.id}, Age: ${this.age}`;
    }

    // Method to update user name
    updateName(newName: string): void {
        this.name = newName;
    }

    // Method to update user age
    updateAge(newAge: number): void {
        this.age = newAge;
    }
}

// Using the UserDetails class with a User type
const user: User = { id: 1, name: "Alice", age: 30 };
const userDetails = new UserDetails(user);

console.log(userDetails.getUserDetails());  // Output: "User: Alice, ID: 1, Age: 30"

// Updating user details
userDetails.updateName("Bob");
userDetails.updateAge(35);

console.log(userDetails.getUserDetails());  // Output: "User: Bob, ID: 1, Age: 35"
console.log(new UserDetails("30"));  // Error: "This will throw error" 
Copy after login
Copy after login

We can use third-party library to determine types at the runtime.

10.Dependency Injection: Dependency Injection is a pattern that allows you to bring code into your component without actually creating or managing it there. While it may seem like using a library, it's different because you don’t need to install or import it via a CDN or API.

At first glance, it might also seem similar to using functions for reusability, as both allow for code reuse. However, if we use functions directly in our components, it can lead to tight coupling between them. This means that any change in the function or its logic could impact every place it is used.

Dependency Injection solves this problem by decoupling the creation of dependencies from the components that use them, making the code more maintainable and testable.

Example without dependency injection

function getProperty<Type>(obj: Type, key: keyof Type) {
  return obj[key];
}

let x = { a: 1, b: 2, c: 3 };
getProperty(x, "a");  // Valid
getProperty(x, "d");  // Error: Argument of type '"d"' is not assignable to parameter of type '"a" | "b" | "c"'.

Copy after login
Copy after login

Example with Dependecy Injection

function func(param:number|boolean){
return param;
}
console.log(func(2)) //Output: 2 will be printed
console.log(func("True")) //Error: boolean cannot be passed as argument

Copy after login
Copy after login

In a tightly coupled scenario, if you have a stressLevel attribute in the MentalWellness class today and decide to change it to something else tomorrow, you would need to update all the places where it was used. This can lead to a lot of refactoring and maintenance challenges.

However, with dependency injection and the use of interfaces, you can avoid this problem. By passing the dependencies (such as the MentalWellness service) through the constructor, the specific implementation details (like the stressLevel attribute) are abstracted away behind the interface. This means that changes to the attribute or class do not require modifications in the dependent classes, as long as the interface remains the same. This approach ensures that the code is loosely coupled, more maintainable, and easier to test, as you’re injecting what’s needed at runtime without tightly coupling components.

The above is the detailed content of Top Advanced typescript concepts that Every Developer Should Know. For more information, please follow other related articles on the PHP Chinese website!

source:dev.to
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template