🎉 I'm releasing 12 products in 12 months! If you love product, checkout my new blog workingoutloud.dev

Back to home

Advanced Typescript Generics In Practice

This post will is 6 of 20 for my series on intermediate-to-advance TypeScript tips.

All tips can be run on the TypeScript Playground.

Introduction

Generics are a tool that gives you the ability to create reusable and flexible types, functions, and classes in TypeScript. A generic type is a type that is connected with another type. It is like a placeholder for any kind of type. In this blog post, we will explore advanced features of TypeScript generics, with hands-on examples you can try out in the TypeScript Playground.

Generic Constraints

Generic constraints in TypeScript allow you to apply certain conditions on the types that are used in your generic functions or classes.

interface Lengthy { length: number; } function countElements<T extends Lengthy>(element: T): number { return element.length; } console.log(countElements("Hello, TypeScript!")); // outputs: 18 console.log(countElements([1, 2, 3, 4, 5])); // outputs: 5

In the above code, T extends Lengthy is a generic constraint that requires the type T to have a length property. The countElements function will now work with any object that has a length property, such as arrays or strings.

See the example here.

Using keyof with Generics

The keyof operator in TypeScript returns a type that represents all possible keys of an object. We can use it in combination with generics to ensure type safety when accessing object properties.

function getProperty<T, K extends keyof T>(obj: T, key: K) { return obj[key]; } let obj = { a: 1, b: 2, c: 3 }; console.log(getProperty(obj, "a")); // outputs: 1 console.log(getProperty(obj, "d")); // Error: Argument of type '"d"' is not assignable to parameter of type '"a" | "b" | "c"'.

In the above example, K extends keyof T is a constraint that makes sure the key exists in the object obj. The TypeScript compiler checks this at compile time.

See the example here.

Generic Classes

In TypeScript, classes can also be generic. Here is an example of a generic Queue class.

class Queue<T> { private data = [] as T[]; push(item: T) { this.data.push(item); } pop(): T | undefined { return this.data.shift(); } } const queue = new Queue<number>(); queue.push(0); queue.push(1); console.log(queue.pop()); // outputs: 0 console.log(queue.pop()); // outputs: 1 queue.push("2"); // TypeError: Argument of type 'string' is not assignable to parameter of type 'number'

In this case, T is a placeholder for any type. We can create a queue of numbers, strings, or any other type. The push and pop methods work with that specific type, ensuring type safety.

See the example here.

Mapped Types with Generics

Mapped types can be combined with generics to create new types based on existing ones.

type ReadOnly<T> = { readonly [P in keyof T]: T[P]; }; const mutableObject = { prop1: "Hello", prop2: "TypeScript", }; const readOnlyObject: ReadOnly<typeof mutableObject> = mutableObject; console.log(readOnlyObject.prop1); // outputs: 'Hello' readOnlyObject.prop1 = "Hi"; // Error: Cannot assign to 'prop1' because it is a read-only property.

In the above example, ReadOnly<T> is a mapped type that takes a type T and creates a new type where all properties are readonly.

Remember, TypeScript generics are all about reusability, flexibility, and maintaining type safety. With a good understanding of generics, you can write more dynamic and reusable code.

See the example here.

Summary

In this blog post, we explored some of the advanced features of TypeScript generics. Generics allow for the creation of flexible, reusable components that maintain type safety, enhancing the development experience.

  1. Generic Constraints enable you to enforce certain conditions on the types used in generic functions or classes.
  2. The keyof operator with Generics provides type safety when accessing object properties, ensuring that the properties you're trying to access do exist on the object.
  3. Generic Classes are about defining classes where certain properties or methods are of a generic type, creating reusable and flexible class structures.
  4. Mapped Types with Generics allow you to create new types based on old ones, offering a way to create more flexible and dynamic type definitions.

These advanced generics techniques empower you to harness the power of TypeScript to create robust and type-safe code. They may appear complex at first, but with practice, they will become a vital tool in your TypeScript arsenal. Keep exploring and happy coding!

Resources and further reading

Photo credit: jeremybishop

Personal image

Dennis O'Keeffe

@dennisokeeffe92
  • Melbourne, Australia

Hi, I am a professional Software Engineer. Formerly of Culture Amp, UsabilityHub, Present Company and NightGuru.
I am currently working on Visibuild.

1,200+ PEOPLE ALREADY JOINED ❤️️

Get fresh posts + news direct to your inbox.

No spam. We only send you relevant content.

Advanced Typescript Generics In Practice

Introduction

Share this post