Tips on efficiently reusing shared components within recursive union types in TypeScript

Summary

Here's a simple working example in the TypeScript playground:

type SimpleExpression = number | string | AddOperator<SimpleExpression> | PrintOperator<SimpleExpression>;
type ExtendedExpression = number | string | AddOperator<ExtendedExpression> | PrintOperator<ExtendedExpression> | PowerOperator<ExtendedExpression>;

However, attempting to extract a common sub-type leads to errors in the playground:

type CommonExpression<E> = number | string | AddOperator<E> | PrintOperator<E>;
type SimpleExpression = CommonExpression<SimpleExpression>;
type ExtendedExpression = CommonExpression<ExtendedExpression> | PowerOperator<ExtendedExpression>;

Is there a solution to this issue?

Detailed Description

With the recent update to TypeScript (3.7), recursive types are now supported, but limitations still exist.
While there are many resources explaining recursive types on StackOverflow, a specific pattern poses a challenge.

The initial concept is an Expression tree, where operators have child expressions. This initial setup works:

type Expression = number | string | AddOperator | PrintOperator;
interface AddOperator {
  'first': Expression;
  'second': Expression;
}
interface PrintOperator {
  'value': Expression;
}

The goal is to create a more generic structure, with SimpleExpression (with 'add' and 'print' operations) and ExtendedExpression (additionally supporting 'power' operations). While this is achievable and functional, the attempt to define a common generic type for both expressions fails in the playground:

type CommonExpression<E> = number | string | AddOperator<E> | PrintOperator<E>;
type SimpleExpression = CommonExpression<SimpleExpression>;
type ExtendedExpression = CommonExpression<ExtendedExpression> | PowerOperator<ExtendedExpression>;

The errors encountered are:

Error: Type alias 'SimpleExpression' circularly references itself.
Error: Type alias 'ExtendedExpression' circularly references itself.

The primary question posed here is: Is there a method to define two distinct expression types that share a common core?

Some context on the usage scenario: the aim is to offer SimpleExpression in a library while enabling users to define additional operators and register handlers for them. The idea is to allow users to easily define their ExtendedExpression type without excessive manual input.

Answer №1

When utilizing

type SimpleExpression = number | string | AddOperator<SimpleExpression> | PrintOperator<SimpleExpression>;
type ExtendedExpression = number | string | AddOperator<ExtendedExpression> | PrintOperator<ExtendedExpression> | PowerOperator<ExtendedExpression>;

there exists a scenario where recursion must come to an end. For instance, with

const simple: SimpleExpression = { value: { value: 5 } };
, classified as
PrintOperator<PrintOperator<number>>
.


However, if you opt for

type CommonExpression<E> = number | string | AddOperator<E> | PrintOperator<E>;
type SimpleExpression = CommonExpression<SimpleExpression>;
type ExtendedExpression = CommonExpression<ExtendedExpression> | PowerOperator<ExtendedExpression>;

recursion never ceases. Essentially, there is a perpetual recursion within the type declaration itself. SimpleExpression becomes

CommonExpression<SimpleExpression>
, which then expands to
CommonExpression<CommonExpression<SimpleExpression>>
, and so forth, leading to an infinite recursion. This is why TypeScript does not approve of this type configuration.


Interestingly,

type SimpleExpression = AddOperator<SimpleExpression> | PrintOperator<SimpleExpression>;

is still permissible, although no variables will fulfill this type.

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

What is the best way to compare an array with comma-separated values in JavaScript?

I have a scenario where I have two arrays, one for categories and the other for products. Each product contains multiple categories as a comma-separated string. My goal is to match a specific category with each product's product_category value and the ...

Transforming TypeScript snapshot data in Firebase Cloud Functions into a string format for notification purposes

I've encountered the following code for cloud functions, which is intended to send a notification to the user upon the creation of a new follower. However, I'm facing an issue regarding converting the snap into a string in order to address the er ...

Unleash the power of zod by seamlessly accessing both parameters and queries

In the zod-middleware documentation, an example is provided: export async function endpointCode(req: TypedRequestBody<typeof bodySchema>, res: Response) { const typedBody = req.body; return res.json(typedBody); } This example demonstrates access ...

What is the best way to execute a function from a parent element within the child element?

Currently, I am working with two components - Navbar (parent) and Setting Menu (Child). const Navbar: React.FC = () => { const classes = useStyles(); const [randomState, setrandomState] = useState(false); const randomStateFunction = () =>{ ...

Utilize PrimeNG's async pipe for lazy loading data efficiently

I have a significant amount of data (400,000 records) that I need to display in a PrimeNG data table. In order to prevent browser crashes, I am looking to implement lazy loading functionality for the table which allows the data to be loaded gradually. The ...

Is it possible to apply a formatting filter or pipe dynamically within an *ngFor loop in Angular (versions 2 and 4

Here is the data Object within my component sampleData=[ { "value": "sample value with no formatter", "formatter": null, }, { "value": "1234.5678", "formatter": "number:'3.5-5'", }, { "value": "1.3495", "formatt ...

Display a single unique value in the dropdown menu when there are duplicate options

Hey there, I'm currently working on retrieving printer information based on their location. If I have multiple printers at the same location, I would like to only display that location once in the dropdown menu. I am aware that this can be resolved at ...

Utilizing React TypeScript within Visual Studio

Utilizing the most recent editions of Visual Studio and Visual Studio Code, I have initiated a project using create-react-app with TypeScript react scripts version. My objective is to host my project within a .NET Core web application that I've estab ...

One-liner in TypeScript for quickly generating an object that implements an interface

In Typescript, you can create an object that implements an interface using a single expression like this: => return {_ : IStudent = { Id: 1, name: 'Naveed' }}; Is it possible to achieve this in just one statement without the need for separate ...

Why does isDisplayed method in Protractor return "No element found using locator" instead of a boolean value?

In my code, I've created a function called isElementDisplayed which features a call to element.isDisplayed. I'm curious as to why the isDisplayed method sometimes returns No element found instead of a boolean value. isElementDisplayed(element: ...

Is it advisable to incorporate 'use server' into every function that retrieves confidential information within server components in Next.js?

By default, server components are enabled in Next.js 13. I'm contemplating whether I should wrap each fetch call in a separate function and include 'use server' to conceal the function's code or if it's acceptable to directly use f ...

Creating a custom type for accepted arguments in a Typescript method

Below is the structure of a method I have: const a = function (...args: any[]) { console.log(args); } In this function, the type of args is any[]. I am looking to create a specific type for the array of arguments accepted by this method in Typescript. ...

What is the best way to exhibit information from a get request within an option tag?

My GET request is fetching data from a REST API, and this is the response I receive: { "listCustomFields": [ { "configurationType": null, "errorDetails": null, "fieldId" ...

Typescript integration with Sequelize CLI for efficient database migrations

According to the Sequelize documentation, it claims to work with Typescript. However, for it to be fully functional in a production environment, DB migration scripts are necessary. The issue arises when using the Sequelize CLI as it only generates and runs ...

When assigning JSON to a class object, the local functions within the class became damaged

This is a demonstration of Object Oriented Programming in JavaScript where we have a parent Class called Book with a child class named PriceDetails. export class Book { name: String; author: String; series: String; priceDetails: Array<Price> ...

Using Angular NgUpgrade to inject an AngularJS service into an Angular service results in an error message stating: Unhandled Promise rejection: Cannot read property 'get' of undefined; Zone:

I have noticed several similar issues on this platform, but none of the solutions seem to work for me. My understanding is that because our Ng2App is bootstrapped first, it does not have a reference to $injector yet. Consequently, when I attempt to use it ...

Typing Redux Thunk with middleware in TypeScript: A comprehensive guide

I recently integrated Redux into my SPFx webpart (using TypeScript), and everything seems to be working fine. However, I'm struggling with typing the thunk functions and could use some guidance on how to properly type them. Here's an example of ...

Troubleshooting: Unable to filter reducers in Redux when using the remove

I'm attempting to eliminate an element from an array using the filter method in this manner: removeDisplate: (state, action: PayloadAction<string>) => { console.log(action.payload); state.map((item) => { console.log(item.name); } ...

Merging two arrays of objects from the same response in JavaScript

How can I efficiently merge two arrays of objects from the same response? let data = [{ "testLevel":"mid", "testId":"m-001", "majorCourse": [ { "courseName":"C++" ...

Connecting the mat-progress bar to a specific project ID in a mat-table

In my Job Execution screen, there is a list of Jobs along with their status displayed. I am looking to implement an Indeterminate mat-progress bar that will be visible when a Job is executing, and it should disappear once the job status changes to stop or ...