Typescript - Creating a generic type that extends its own type signature

Recently, I stumbled upon the following code snippet:

interface Test<T extends Test<T>> {
  a: number;
  b: T;
}

function foo <T extends Test<T>>(el: T): T {
  ...
}

I must admit, I am a bit perplexed by this and wondering about its purpose. I've reviewed the Generics section of the Typescript handbook but couldn't find anything similar.

What is the significance of that interface that cannot be accomplished with something like the following?

interface Test<T>

If anyone could provide some insight on this matter, it would be greatly appreciated!

Answer №1

In the absence of a specific example, I can only provide general information. The kind of syntax you are referring to is commonly used in languages like Java that lack polymorphic this types, which I will explain briefly.


The concept revolves around defining a generic type that points to other objects of the same type as its parent class or interface. Let's take a look at your Test interface:

interface Test<T extends Test<T>> {
  a: number;
  b: T;
}

This sets up a structure resembling a linked list where the b property of a Test<T> must be another Test<T>, since T extends Test<T>. Moreover, it must also be (or a subtype of) the same type as the parent object. Here are two implementations to illustrate this:

interface ChocolateTest extends Test<ChocolateTest> {
  flavor: "chocolate";
}
const choc = {a: 0, b: {a: 1, flavor: "chocolate"}, flavor: "chocolate"} as ChocolateTest;
choc.b.b = choc;

interface VanillaTest extends Test<VanillaTest> {
  flavor: "vanilla";
}
const vani = {a: 0, b: {a: 1, flavor: "vanilla"}, flavor: "vanilla"} as VanillaTest;
vani.b.b = vani;

While both ChocolateTest and VanillaTest are implementations of Test, they are not interchangeable. The b property of a ChocolateTest is a ChocolateTest, whereas for a VanillaTest, it is a VanillaTest. Attempting to assign one to the other results in an error, thereby maintaining consistency.

choc.b = vani; // Error!

By using this approach, you can confidently navigate through the chain knowing each instance belongs to the same type:

choc.b.b.b.b.b.b.b.b.b // <-- still a ChocolateTest

Contrast this with another interface setup:

interface RelaxedTest {
  a: number;
  b: RelaxedTest;
}

interface RelaxedChocolateTest extends RelaxedTest {
  flavor: "chocolate";
}
const relaxedChoc: RelaxedChocolateTest = choc;

interface RelaxedVanillaTest extends RelaxedTest {
  flavor: "vanilla";
}
const relaxedVani: RelaxedVanillaTest = vani;

Here, RelaxedTest does not enforce the b property to be identical to the parent type but rather any implementation of RelaxedTest. Consequently, assigning instances between different flavors is permissible:

relaxedChoc.b = relaxedVani; // No error

This difference arises from the flexibility provided by RelaxedTest, allowing compatibility between various implementations irrespective of their exact types. On the contrary, with

Test<T extends Test<T>>
, such freedom is restricted based on the defined relationships.


The ability to specify a type that mandates consistency throughout the chain proves beneficial. TypeScript offers a feature known as polymorphic this designed precisely for this scenario. By utilizing this as a type representing "the same type as the containing class/interface," one can simplify the previous generic structure:

interface BetterTest {
  a: number;
  b: this; // <-- same as the implementing subtype
}

interface BetterChocolateTest extends BetterTest {
  flavor: "chocolate";
}
const betterChoc: BetterChocolateTest = choc;

interface BetterVanillaTest extends BetterTest {
  flavor: "vanilla";
}
const betterVani: BetterVanillaTest = vani;

betterChoc.b = betterVani; // Error!

This revised approach mirrors the original

Test<T extends Test<T>>
without delving into potential circular dependencies. Hence, opting for polymorphic this presents a cleaner alternative unless there are specific reasons demanding otherwise.

If you encountered code structured differently, perhaps due to historical factors or unawareness of newer features like polymorphic this, consider this updated perspective. Good luck navigating through these concepts!


I hope this explanation clarifies the topic and aids in your understanding. Best wishes!

Answer №2

function retrieveData<TType extends number | string, T extends Tree<TType>>(data: T[]): T[] {
    console.log(data[0].key);
    return
}


export interface Tree<T> {
    label?: string;
    data?: any;
    parent?: Tree<T>;
    parentId?: T;
    key?: T;
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

A step-by-step guide on bundling a TypeScript Language Server Extensions LSP using WebPack

Currently, I am working on a language server extension for vs-code that is based on the lsp-sample code. You can find the code here: https://github.com/microsoft/vscode-extension-samples/tree/master/lsp-sample My challenge lies in WebPacking the extension ...

Utilizing React and Typescript to create custom attributes

With React Typescript, it is possible to include custom data-* attributes. But the question arises, can we also add other custom attributes such as 'name' or 'test'? <span name="I'm causing a type error" data-test="I'm Wor ...

Angular2 Error: Cannot have two identifiers with the same name, 'PropertyKey' is duplicated

I am currently developing an application with angular2 using angular-cli. Unfortunately, angular-in-memory-web-api was not included by default. After some research, I manually added the line "angular-in-memory-web-api": "~0.1.5" to my ...

Contrasting the utilization of `typeof` with a constant and `enum` in TypeScript

Can you explain the distinction between using typeof with a constant and an enum in TypeScript? For example: const TYPE_A = 'a' const TYPE_B = 'b' type MyType = | typeof TYPE_A | typeof TYPE_B type Result = { name: string type ...

Setting angular variables by assigning form values

My current reactive form setup looks like this: this.editform = new FormGroup({ 'username' : new FormControl(null,[Validators.required]), 'password' : new FormControl(null,[Validators.required]), 'full_name' : ne ...

Ensure the security of a generic type's value by enforcing type safety

Is there a way to utilize generics to ensure that the type of a value is specific? // Sample array const testArr = [ { id: 3, name: 'Spaghetto', // Type 'string' here shouldNotWork: 3.14, // Type 'number' here ...

A more concise validation function for mandatory fields

When working on an HTML application with TypeScript, I encountered a situation where I needed to build an error message for a form that had several required fields. In my TypeScript file, I created a function called hasErrors() which checks each field and ...

Is there a sweet TypeScript class constructor that can take in its own instance as an argument?

I have a scenario where I need to read in instances of Todo from a CSV file. The issue is that Papaparse does not handle dynamic conversion on dates, so I'm currently dropping the object into its own constructor to do the conversion: class Todo { ...

"Slow loading times experienced with Nextjs Image component when integrated with a map

Why do the images load slowly on localhost when using map, but quickly when not using it? I've tried various props with the Image component, but none seem to solve this issue. However, if I refresh the page after all images have rendered once, they ...

Encountering issues with React Nextjs - unable to utilize useState hook or

Hi everyone, I'm new to React and I think I might have overlooked something. I've been trying to create a simple registration form, but it seems like I'm having trouble changing the state. ` import {useState} from 'react' export ...

Utilizing Typescript template strings for data inference

In coding practice, a specific convention involves denoting the child of an entity (meaning an association with another entity) with a '$' symbol. class Pet { owner$: any; } When making a reference to an entity child, users should have the opt ...

Facing issue with local redis session not functioning as intended

I'm encountering an issue with my redis session not functioning properly when testing locally. EDIT: Additionally, I realized that it's failing to save a cookie when trying to set req.session[somekey] as undefined like so: req.session.user = u ...

React - Incorrect use of hooks and class components

Understanding hooks as the opposite of a class component can be confusing. A simple line of code can trigger an error when trying to adapt it for use in a class component. Take, for example, this situation: .jsx files and functional components work seamles ...

"Error: The React TypeScript variable has been declared but remains

Seeking guidance on ReactTS. I'm puzzled by the undefined value of the variable content. Could someone shed light on why this is happening and how to assign a value to it for passing to <App />? The issue persists in both the index.tsx file and ...

Executing functions in Vue TypeScript during initialization, creation, or mounting stages

Just a few hours ago, I kicked off my Vue TypeScript project. I've successfully configured eslint and tslint rules to format the code as desired, which has left me quite pleased. Now, I'm curious about how to utilize the created/mounted lifecycl ...

Issue with handsontable numbro library occurs exclusively in the production build

Encountering an error while attempting to add a row to my handsontable instance: core.js.pre-build-optimizer.js:15724 ERROR RangeError: toFixed() digits argument must be between 0 and 100 at Number.toFixed () at h (numbro.min.js.pre-build-op ...

What is the best approach to incorporate a stopwatch?

I'm exploring ways to track the time it takes for a user to click a button. While I have a working solution, I'm curious if there's a more efficient method available. Below is my current implementation: export class MainComponent implements ...

I'm looking to switch out the `~` to turn it into a URL for the backend, seeing as `<img alt="" src="~/media/Banner/plane.JPG">` is returning a 404 error

1. Received an HTTP Request and JSON response from the backend localhost:3000 (entered value using wysiwyg) { "Description": "&lt;img alt=&quot;&quot; src=&quot;~/media/Banner/plane.JPG&quot; /&gt;1 test berita&lt ...

What steps can be taken to resolve the error message "How can you repair 'Cannot read properties of undefined (reading 'front_default')'?"

I'm encountering an issue while trying to display data from an API. Although I am able to access the data, a perplexing error keeps popping up that I can't seem to troubleshoot. Here's the error message: Uncaught TypeError: Cannot read pr ...

1. "The power of three vows in the world

I encountered an issue while making three HTTP Post requests in my code. The first two requests are successful, and upon debugging the code, they return the correct values. However, the third request returns undefined. The reason behind making these three ...