TypeScript Generics

Pieces 🌟 - Sep 11 '23 - - Dev Community

Due to the complexities in the logic of modern web applications and the quest for efficient code implementation, the need to learn TypeScript has become increasingly important, especially in web development. As it’s built on the syntax and logic of JavaScript, the migration from JavaScript to TypeScript is not as difficult as many may think, since it's basically adding types to your JavaScript.

However, it doesn’t end there. We often hear how TypeScript is JavaScript with types and additional features but no one talks about these "additional features", almost as though they are afraid of what they might find. To aid an application's flexibility and long-term scalability, Generics was introduced as a tool in web development to reuse components.

TypeScript Generics is a term that might seem confusing to JavaScript developers because it is one of the features that JavaScript does not have. Being a web developer in 2023 means you are well-skilled and experienced across many languages and tools — let's face it, sometimes it gets tiring to learn yet another feature.

Not to worry, because in this article, you will learn all about TypeScript Generics, what they are used for, the benefits of using generics, and how to create generic functions, generic classes, and generic interfaces in TypeScript. Like always, to ease your learning process, Pieces for Developers can help.

What is TypeScript Generics?

Here's a funny scenario: A TypeScript developer is talking to a JavaScript developer about their favorite features to use in web development. While chatting, they mention the term "generics." The JavaScript developer, confused, asks what generics are. The TypeScript dev replies: TypeScript Generics is a tool in web development for creating reusable components. It helps developers create components that work over several data types instead of explicitly defining each type for a particular component.

Suppose a developer creates a component of a number type, but later on in the codebase, he wants to reuse the functionality of that component to accept input for a different data type, say a string. Instead of toggling the component so it accepts the string data type, TypeScript Generics offers the flexibility of accepting a variety of types for a single component. Interesting, isn't it? So, if you have been looking for a way to do this, you have come to the right place!

Why Should you use TypeScript Generics?

Code reusability is one of the benefits of using TypeScript Generics, but that's not all. Other major benefits include:

  • Compile Time Type Checking: With the use of generics, you have to specify the data type after defining a variable, function, or class. The specified data types are checked before it gets to the compiler. This saves development time as type errors are highlighted earlier.
  • Improved Code Readability: A key engineering practice to adopt when working on large projects or in teams is ensuring easy code readability. The use of generics improves code readability since TypeScript Generic types are explicitly defined.
  • Elimination of Type Inference: Rather than allowing the TypeScript Compiler to infer types through type inference, generics eliminates the need for type inference. This improves code safety.
  • Elimination of any type: One way to ensure a variable is compatible with several types is to give it a type of any. This tells the compiler, "Hey, this variable should work with any data type!" However, it is bad practice, as type-checking is not done and this could lead to unwanted results. Generics provide a way to accept multiple data types while ensuring type safety.

Basic Syntax and Working Principle

As we have learnt that the use of generics provides a way to accept multiple data types for a single variable rather than the use of the any keyword. Consider the block of code below:

// type of number is defined
let size: number = 12;
// size accept type of string?
let size = "Twelve";
// error
Enter fullscreen mode Exit fullscreen mode

Save this code

In the above code snippet, we specify the type of the variable size to be a number with a value of 12, we then try to reassign the variable to the value of 12, but in string format. It throws an error because we try to assign the variable to a string type of Twelve when it has been initially defined to accept the number data type. How do we implement an instance where we want our variable to accept both types; number and string?

Use of any data type

One way to assign values of multiple data types to a single variable is the use of the any data type. Consider the block of code below:

//code sample

let sample: any;

//string data type
sample =
  "Convert from one programming language to another using the Pieces App";

//number data type
sample = 2;

//
sample = true;
Enter fullscreen mode Exit fullscreen mode

Save this code

Here, we create a variable named sample with the any type. This means the variable can accept any data type. In our code snippet, we assign a string type that says "Convert from one programming language to another using the Pieces App", a number type of value 2, and a boolean type of value true.

In TypeScript, assigning values of a different data type to a pre-defined variable will throw an error, but the any type provides a solution for that. The use of the any data type, however, is not recommended because it bypasses type-checking and poses a risk of type mismatch at runtime.

Use of Generics - Generic Functions

Another way TypeScript allows multiple data types over a single variable is through the use of generics. Generic functions form the bulk of the usage of generics. Suppose we want to create a function in TypeScript that accepts a type of number. Here's how we go about it:

function numberType(arg: number): number {
  return arg;
}
Enter fullscreen mode Exit fullscreen mode

Here, to avoid an error being thrown, the type of the value returned must be a number. Sometimes, we do not know the exact value that will be returned. The use of the any type will make the value to be accepted by the function but we won't know the type returned. That's where the use of generics in TypeScript comes in. It allows any data type to be accepted while specifying the type that was accepted. Let's see how we would go about this:

function anyType<Type>(arg: Type): Type {
  return arg;
}
Enter fullscreen mode Exit fullscreen mode

In the above code snippet, we added a Type variable of Type embedded in <>, which is the basic syntax of TypeScript generics. When a value is returned in the function, the Type makes it possible to know the exact type that was returned. This is one of the benefits of using TypeScript Generics— the eradication of the need for the any type.

Creating a Generic Function

To properly understand the need for generics in our codebase, we will be creating a basic TypeScript generic function that accepts data of different types. As we have discussed earlier, one way to do this is to use the any type. In this section, we will be discussing how to achieve this using generics. Consider the block of code below:

function genericsSample<T>(items: T[]): T[] {
  return new Array<T>().concat(items);
}
let numType = genericsSample<number>([10, 20, 30]);
let stringType = genericsSample<string>([
  "Pieces app",
  "A tool for developers",
]);
numType.push(80); // Correct
numType.push("Copy and save code snippets"); // Error
stringType.push(
  "Convert from one programming language to another using the Pieces App",
); // Correct
stringType.push(90); //Error
console.log(numType);
console.log(stringType);
Enter fullscreen mode Exit fullscreen mode

Save this code

In the block of code above, we create a generic function that concatenates two items. This code snippet aims to accept data from two types, a number and a string. The major difference between this code snippet with generics and a code snippet with the any type is that generic functions validate the data type from the user.

In this code snippet, we create a generic function named genericsSample that returns an array of types. After that, we create two variables, numType and stringType. numType accepts an array of numbers, while stringType accepts an array of strings. The reason for this is to validate the user's input just before it logs into the console.

After successfully creating the variables, we test if it works by trying to add new items to the TypeScript Generic array we have created. We add a string and number to both the numType and stringType. The two strings are "Copy and save code snippets" and "Convert from one programming language to another using the Pieces App" which are some of the use cases of Pieces.

In our output, we see that it throws an error when we try to add a string to a numType and when we try to add a number to a stringType. Let's run our code and see the output. I will be using the online TypeScript Playground editor to run and compile this code. Here is the output:

Compiling the code throws an error.

Here, we see that an error is thrown in the first instance of passing a string to the numType variable and another error is thrown when we pass a number to the stringType variable. With the help of generics, our code validates the input of multiple data types in a single function.

Generic Classes

In addition to creating functions, we can also create a TypeScript generic class. After we define the name of the class, we specify the generic type parameter in angle brackets (<>). Consider the block of code below:

class GenericClass<T> {
  private genericField: T;

  constructor(value: T) {
    this.genericField = value;
  }

  getValue(): T {
    return this.genericField;
  }
}

//creating a class with a number type
const numType = new GenericClass<number>(27);
const getNum: number = numType.getValue();
console.log(getNum);

//creating a class with a string type
const stringType = new GenericClass<string>(
  "Convert from one programming language to another using the Pieces App",
);
const getString: string = stringType.getValue();
console.log(getString);
Enter fullscreen mode Exit fullscreen mode

Save this code

Here, to accept multiple types in our class, we create a generic class in TypeScript of type parameter "T", so we can specify whatever type we want to pass into it (number, string, or any other type). In the first instance of this code, we specify the value of our GenericClass should be a number type of 27. In the second instance, we specify the value of GenericClass should be a string that says "Convert from one programming language to another using the Pieces App."

Here is the output:

Successfully compiling the code.

TypeScript Generic Interface

Generic types are also supported for Interfaces in TypeScript. This lets us create interfaces that accept multiple data types. Suppose we want to create a generic TypeScript interface that accepts two types, a number and a string. Here's how we do it:

interface interfaceSample<T, U> {
  first: T;
  second: U;
}

const interfaceOne: interfaceSample<number, string> = {
  first: 20,
  second: "10",
};
const interfaceTwo: interfaceSample<string, boolean> = {
  first: "Generate code snippets using Pieces Co Pilot",
  second: true,
};

console.log(interfaceOne);
console.log(interfaceTwo);
Enter fullscreen mode Exit fullscreen mode

Save this code

Here, we create a TypeScript generic type interface with two type parameters, "T" and "U". This stands as a placeholder for the types we will pass in. In the interfaceOne variable, we pass in the number type and the string type. In the interfaceTwo variable, we pass in a boolean type and a string type that says "Generate code snippets using Pieces Copilot."

TypeScript Generic Constraints

While generics work with all and any type, sometimes we may want to restrict our function, class or interface to work with just a single type. We achieve this by making use of a TypeScript generic constraint. We can demonstrate this using a sample interface:

interface ConstraintSample {
  sample: number;
}

function loggingIdentity<Type extends ConstraintSample>(test: Type): Type {
  console.log(test.sample); //
  return test;
}
loggingIdentity({ sample: 15, value: 10 });
Enter fullscreen mode Exit fullscreen mode

Save this code

Normally, to get the value of 10, all we have to do is call out the loggingIdentity(10) function, but this wouldn't work in this case because we have added a generic constraint that adds extra specificity to the types we are working with. Here, to get the value of 10, we have to first specify that there is a property of sample in the value we are trying to call. To call any value in a constraint, we need to specify all the properties associated with that value.

TypeScript Generics x Pieces App

The concept of generics is new to JavaScript developers or developers just getting started with TypeScript. Sometimes it may get overwhelming, but we're here to help! To bridge the gap in learning, the Pieces Desktop App provides a feature where you can copy and save code snippets of TypeScript Generics anywhere on the web and you get insights on the snippet. Isn't that interesting? For example, with just a single click, we can save one of the code snippets in this article. Let's see how it works:

A code snippet saved in Pieces.

Here, I saved a code snippet from our Generic Interface section. This is what happened the moment I clicked on the "Copy and Save" option (this option appears over code snippets when you have the Pieces Chrome Extension installed on your PC):

  • The code snippet automatically reflects on the Pieces App.
  • A title describing the code snippet is provided. In this instance, it names our code snippet "Generic Interface with Multiple Type Parameters", precisely what we achieved with the code snippet.
  • A context preview offers suggested searches, the site the code snippet was copied from, related tags, and a brief description of what the code does. So, if you're new to generics and you come across a piece of code you don’t understand, you're one click away from getting all this help!
  • Also, Pieces has a TypeScript snippet collection of sample code that helps you fill up your personal repository quicker! Ready to get started? Install the Pieces Desktop App for free.

Conclusion

In this article, you have learned about TypeScript Generics, the benefits of TypeScript Generics, and how to create generic functions, classes, and constraints in TypeScript. Additionally, we provided a means of speeding up your development time with TypeScript Generics using the Pieces App. Have fun!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .