Master “Generics” In Typescript🎉

Generics in Typescript allows you to create a component that can work with different types. This lets users use these components with their specific type.

Before reading this article, please read my article about TS utility types as It will help you t…

Generics in Typescript allows you to create a component that can work with different types. This lets users use these components with their specific type.

Before reading this article, please read my article about TS utility types as It will help you to understand this article better.

Table of contents

  • 1. Generics with type
  • 2. Generic functions
  • 3. Generics with built-in functions
  • 4. Inferring the types
  • 5. Constraints on type arguments
  • 6. Constraints in functions
  • 7. Use as when necessary
  • 8. Multiple generics
  • 9. Defaults in type arguments
  • 10. Class Types in Generics
  • Conclusion

Here are few possible ways to use Generics in Typescript:

1. Generics with type

The easiest way of using generics is with type. For example:

type MyData<Type> = {
  data: Type
}

type DataAsString =  MyData<string>;
// type DataAsString = {
//    data: string;
// }

Because we passed string as a type parameter, our data will be string type. Likewise, if we pass the number type, our data will be of number type. And that’s the beauty of generics. You can work with any type.

type MyData<Type> = {
  data: Type
}

type DataAsString =  MyData<number>;
// type DataAsString = {
//    data: number;
// }

2. Generic functions

As you can pass arguments to a function, you can also pass type arguments to a function. For example:

const fetchData = <Type>(url: string): Promise<Type> => {
    return fetch(url)
}

fetchData<{name: string, age: number}>("/api/books")
  .then(res => {
      console.log(res)
      // res: {name: string, age: number}
  })

We passed {name: string, age: number} as a type argument to the fetchData function, And this type argument tells res what It is supposed to be because this is what’s getting typed in Promise<Type>. So now the function fetchData has become a generic function. A generic function is just a normal function but includes a type argument.

If we want to pass another type, our res will replicate that same type.

const fetchData = <Type>(url: string): Promise<Type> => {
    return fetch(url)
}

fetchData<{id: number, email: string}>("/api/books")
  .then(res => {
      console.log(res)
      // res: {id: number, email: string}
  })

3. Generics with built-in functions

You can even use generics with built-in functions. For example, you can create a Set that stores only numbers by using the Set constructor with a type parameter:

const numberSet = new Set<number>();
numberSet.add(1);
numberSet.add(2);
numberSet.add(3);

Similarly, you can create a Map that maps strings to numbers by using the Map constructor with type parameters for the key and value types:

const stringToNumberMap = new Map<string, number>();
stringToNumberMap.set("one", 1);
stringToNumberMap.set("two", 2);
stringToNumberMap.set("three", 3);

4. Inferring the types

If your type argument looks similar to your runtime argument then you don’t have to pass a generic type to your function. For example:

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

const result1 = identity<number>(42); // const result1: number
const result2 = identity<string>("hello"); // const result2: string

While the above example looks correct, we can simplify it more:

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

const result1 = identity(42); // const result1: number
const result2 = identity("hello"); // const result2: string

In this case, because we are not passing any type argument, then Typescript will look in the runtime argument to see If It can infer anything from it.
So the return type is inferred from the runtime argument type, and the function returns the same type as it receives.

5. Constraints on type arguments

In our following example, we wanted to be able to use the ReturnType utility type, but the compiler could not prove that every type was a function; it could be a string or number, and as we know, that ReturnType works only with functions, so it warns us that we can’t make this assumption.

type GetPromiseData<T> = Awaited<ReturnType<T>>
// Error: Type 'T' does not satisfy the constraint '(...args: any) => any'

type PromiseResult = GetPromiseData<
  () => Promise<{
    id: string
    email: string
  }>
>

So to make It clear to Tyepscript, We have to say that T is only a function, and we can do this by adding extends (...args: any) => any after the T:

type GetPromiseData<T extends (...args: any) => any> = Awaited<ReturnType<T>>

type PromiseResult = GetPromiseData<
  () => Promise<{
    id: string
    email: string
  }>
>

6. Constraints in functions

In our following example, we wanted to access the .address property of arg, but the compiler could not prove that every type has a .address property, so it warned us that we couldn’t make this assumption.

function myFunc<Type>(arg: Type): Type {
  console.log(arg.address); // Property 'address' does not exist on type 'Type'.
  return arg;
}

To fix this error, we can create an interface with address property in it and extend it with the Type argument:

interface IDetails {
  address: string;
}

function myFunc<Type extends IDetails>(arg: Type): Type {
  console.log(arg.address); // Now we know it has a .address property, so no more error
  return arg;
}

Because the generic function is now constrained, it will no longer work over all types:

myFunc(true);
// error: Argument of type 'boolean' is not assignable to parameter of type 'IDetails'.

Instead, we need to pass in values whose type has address property:

myFunc({ length: 10, address: "Something" });

7. Use as when necessary

Sometimes using as is the best thing you can do when using generics. For example:

const ObjectKeys = <T extends {}>(obj: T): Array<keyof T> => {
  return Object.keys(obj) // Error: Type 'string[]' is not assignable to type '(keyof T)[]'. 
}

const result = ObjectKeys({
  id: 6,
  email: "me@gmail.com"
})

Here we are getting an error because the Object.keys method returns an array of string values, and TypeScript cannot guarantee that the string values returned by Object.keys actually correspond to valid keys of the generic type T.

To fix this error, we can explicitly typecast the Object.keys result to an array of keyof T using the as keyword:

const ObjectKeys = <T extends {}>(obj: T) => {
  return Object.keys(obj) as Array<keyof T>
}

const result = ObjectKeys({
  id: 6,
  email: "me@gmail.com"
})

8. Multiple generics

Sometimes we have to use multiple generics to make sure that we are getting back a certain type. Consider the following example:

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

let x = { a: 1, b: "b", c: true, d: 4 };

getProperty(x, "a"); // return type: string | number | boolean

While the above example is correct, the problem is that the return type is not explicit. Return type is an union consisting of 3 different types. To fix this problem, we can use multiple generic types:

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

let x = { a: 1, b: "b", c: true, d: 4 };

getProperty(x, "a"); // return type: number

In this example, we’ve added generic type parameter Key to represent the key type of the object T. So now we will get a specific return type only.

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

let x = { a: 1, b: "b", c: true, d: 4 };

getProperty(x, "c"); // return type: boolean

9. Defaults in type arguments

We can also use default types in generics. Consider the following example:

const makeSet = <T>() => {
  return new Set<T>()
}

const mySet = makeSet() // const mySet: Set<unknown>

Here we are getting unknown because we didn’t pass any type argument to makeSet function. We can solve this problem by either passing a type argument like this, makeSet<number>() or by specifying a default type:

const makeSet = <T = number>() => {
  return new Set<T>()
}

const mySet = makeSet() // const mySet: Set<number>

10. Class Types in Generics

We can also refer to class types by their constructor functions. For example:

function create<Type>(c: { new (): Type }): Type {
  return new c();
}

Here’s a breakdown of how the function works:

  • The create function is declared with a type parameter Type, representing the type the function will make.

  • The function takes a single argument c, an object representing a constructor function for the type Type. The argument has the type { new (): Type }, an object type specifying a constructor function that takes no arguments and returns a value of type Type.

  • The new keyword is used to create a new instance of the type Type inside the function by calling the constructor function passed as the argument c. The new keyword creates a further object of the type Type and returns it as the result of the function.

  • The function’s return type is specified as Type, which ensures that the function returns an instance of the type specified by the type parameter.

Here’s an example of how the create function can be used:

class MyClass {
  constructor(public value: string) {}
}

const instance = create(MyClass);
console.log(instance.value); // Output: undefined

This example defines a simple class, MyClass, with a single property value. We then call the create function, passing the MyClass constructor function as the argument. The create function creates a new instance of MyClass using the constructor function and returns it as an instance of type MyClass.

Conclusion

In conclusion, TypeScript’s support for generics provides a powerful tool for writing type-safe and reusable code. By defining generic types and functions, you can create code that works with various types while maintaining strict type-checking.

To make the best use of generics in TypeScript, it’s essential to understand how to define and use generic types, specify constraints on generic type parameters, and use type inference to reduce the need for explicit type annotations. Additionally, it’s crucial to use generics to maintain good code readability and avoid unnecessary complexity.

When appropriately used, generics can significantly improve the quality and maintainability of your TypeScript code. By taking advantage of TypeScript’s powerful type system and the flexibility of generic types, you can create highly reusable and expressive code while still maintaining the strong type safety that TypeScript provides.

Visit:
👨‍💻My Portfolio
🏞️My Fiverr
🌉My Github
🧙‍♂️My LinkedIn


Print Share Comment Cite Upload Translate Updates
APA

Arafat | Sciencx (2023-05-11T09:44:43+00:00) Master “Generics” In Typescript🎉. Retrieved from https://www.scien.cx/2023/05/11/master-generics-in-typescript%f0%9f%8e%89/

MLA
" » Master “Generics” In Typescript🎉." Arafat | Sciencx - Thursday May 11, 2023, https://www.scien.cx/2023/05/11/master-generics-in-typescript%f0%9f%8e%89/
HARVARD
Arafat | Sciencx Thursday May 11, 2023 » Master “Generics” In Typescript🎉., viewed ,<https://www.scien.cx/2023/05/11/master-generics-in-typescript%f0%9f%8e%89/>
VANCOUVER
Arafat | Sciencx - » Master “Generics” In Typescript🎉. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2023/05/11/master-generics-in-typescript%f0%9f%8e%89/
CHICAGO
" » Master “Generics” In Typescript🎉." Arafat | Sciencx - Accessed . https://www.scien.cx/2023/05/11/master-generics-in-typescript%f0%9f%8e%89/
IEEE
" » Master “Generics” In Typescript🎉." Arafat | Sciencx [Online]. Available: https://www.scien.cx/2023/05/11/master-generics-in-typescript%f0%9f%8e%89/. [Accessed: ]
rf:citation
» Master “Generics” In Typescript🎉 | Arafat | Sciencx | https://www.scien.cx/2023/05/11/master-generics-in-typescript%f0%9f%8e%89/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.