What is the best way to write a function that takes a mixin function as input parameter?

In my code, there is a function named Mixin which requires a single argument in the form of a "class factory mixin".

For example, let's consider a scenario where I have a class factory mixin function like this:

type Constructor<T = any, A extends any[] = any[]> = new (...a: A) => T

const CoolMixin = <T extends Constructor>(Base: T) => {
  return class CoolMixin extends Base {
    coolProp = 42
  }
}

const CoolFoo = CoolMixin(class Foo {
    foo = 'asdf'
})

const c = new CoolFoo()

// it works:
c.foo
c.coolProp

By observing the above function, it can be noted that it accepts a base class and returns a new class, functioning effectively.

To enhance this functionality, I have developed a Mixin utility that provides additional features such as hasInstance support, caching against duplicate applications of base classes, and other functionalities.

In plain JavaScript, I utilize this utility as follows:

// Mixin returns an application of the Mixin function (a class) with
// a default base class applied (Object by default):
const CoolMixin = Mixin((Base) => {
  return class CoolMixin extends Base {
    coolProp = 42
  }
})


// Here, CoolMixin is `class CoolMixin extends Object {...}`,
// thus allowing its usage similar to a regular class:
let CoolFoo = class Foo extends CoolMixin {
    foo = 'asdf'
}


// Mixin returns that class with a static `.mixin` property containing
// the original mixin function, enabling its utilization as a mixin:
CoolFoo = CoolMixin.mixin(class Foo {
    foo = 'asdf'
})

// Both versions work identically:
const c = new CoolFoo()
c.foo
c.coolProp

Hence, the advantage of my utility lies in its flexibility for convenient use. Here are two more examples:

// Suppose One and Two are mixins created using my Mixin utility.

// Use regular extension:
class Foo extends One {...}
class Bar extends Two {...}

// Or combine them together:
class Baz extends One.mixin(Two) {...}

Therefore, I aim to establish typing for this Mixin utility in TypeScript.

My initial attempt showcases how I am trying to achieve this concept:

type Constructor<T = any, A extends any[] = any[]> = new (...a: A) => T

type MixinFunction = <TSub, TSuper>(base: Constructor<TSuper>) =>
  Constructor<TSub & TSuper>

declare function Mixin<TSub, TSuper, T extends MixinFunction>(mixinFn: T):
  Constructor<TSub & TSuper> & {mixin: T}

// Using it as follows:

const CoolMixinFunction = <T extends Constructor>(Base: T) => {
  return class CoolMixin extends Base {
    coolProp = 42
  }
}

const CoolMixin = Mixin(CoolMixinFunction)

const CoolFoo = CoolMixin.mixin(class Foo {
    foo = 'asdf'
}

const c = new CoolFoo()
c.foo
c.coolProp

const CoolBar = class Bar extends CoolMixin {
    bar = 'asdf'
})

const b = new CoolBar()
b.bar
b.coolProp

Through this process, I intend to type the Mixin tool to accept a mixin function, ensuring that the returned class from the Mixin call is generated according to the principles laid out by the mixin function. Additionally, the generated class should feature a .mixin property identical to the entered mixin function.

Despite realizing that there may be flaws in my approach, I seek guidance on implementing type inference effectively.

Given the latest advancements like the "Higher order function type inference" feature, I believe incorporating such tools could prove beneficial in this context.

How can I refine the typing for this Mixin utility? Is it feasible without leveraging the higher-order feature mentioned earlier? If not, how would one integrate it using said advancements?

Answer №1

The successful compilation of the code and proper functioning of all prop types are satisfying outcomes, although the semantics behind class Bar extends CoolMixin remain unclear to me. It seems that extending the mix-in directly implies using the mixin class as the base class without any prior application.

type Constructor<T = any, A extends any[] = any[]> = new (...a: A) => T

// The function is not generic on two type parameters:
// it is a generic type on TSub as that is fixed during definition
// and a generic function on TSuper as that is defined during the mix-in call
// although TSub does not much matter so we can erase it
type MixinFunction = <TSuper>(base: Constructor<TSuper>) => Constructor<TSuper>

declare function Mixin<T extends MixinFunction>(mixinFn: T): ReturnType<T> & { mixin: T }

// Then using it like so:
// The {} in the extends is critical to allow ReturnType above to get an insatnce of mixin as if applied to {}.
const CoolMixinFunction = <T extends Constructor<{}>>(Base: T) => {
    return class CoolMixin extends Base {
        coolProp = 42
    }
}

const CoolMixin = Mixin(CoolMixinFunction)

const CoolFoo = CoolMixin.mixin(class Foo {
    foo = 'asdf'
})

const c = new CoolFoo()
c.foo
c.coolProp

const CoolBar = class Bar extends CoolMixin {
    bar = 'asdf'
}

const b = new CoolBar()
b.bar
b.coolProp

(playground link)

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

The module "ng-particles" does not have a Container component available for export

I have integrated ng-particles into my Angular project by installing it with npm i ng-particles and adding app.ts import { Container, Main } from 'ng-particles'; export class AppComponent{ id = "tsparticles"; /* Using a remote ...

Can you provide guidance on adjusting the dimensions of the Carousel element within the ShadCN UI?

My React component setup currently includes the following: "use client"; import Autoplay from "embla-carousel-autoplay"; import { Card, CardContent } from "@/components/ui/card"; import { Carousel, CarouselContent, ...

Using Angular 6 to Format Dates

Can anyone provide instructions on how to achieve the following format in Angular? Expected: 20JAN2019 Currently, with the default Angular pipe, I am getting: 20/01/2019 when using {{slotEndDate | date:'dd/MM/yyyy'}} Do I need to write a ...

Is there a way to order the execution of two functions that each produce promises?

With my code, I first check the status of word.statusId to see if it's dirty. If it is, I update the word and then proceed to update wordForms. If it's clean, I simply update wordForms. I'm looking for advice on whether this is the correct a ...

`Is There a Solution When Compilation Fails?`

I keep encountering an issue when I run the command npm start. The problem seems to be originating from PancakeSwap Frontend and after several attempts, I am still unable to resolve it. Your assistance is greatly appreciated :) Below is a snippet of my Ap ...

Guidance on implementing a Cypress assertion for a JavaScript object retrieved via ag-Grid

Seeking guidance as I navigate the world of UI automation and Cypress, specifically in setting up assertions on JavaScript objects returned by the cypress-ag-grid package Currently, my code is extracting data from ag-grid cy.get("#myGrid").getAg ...

Using TypeScript to define a static enum within a React functional component

Creating a React.FunctionalComponent with static props: import MyAwesomeComponent from './MyAwesomeComponent'; ... <MyAwesomeComponent mode={MyAwesomeComponent.modes.superAwesome} /> Static props defined as key-value pairs: MyAwe ...

Upon completion of a promise in an express middleware and breaking out of a loop, a 404 error is returned

In my efforts to retrieve an array of object (car) from express using database functions in conjunction with the stolenCarDb object, everything seems to be working fine. However, when attempting the following code snippet, it results in a 404 error w ...

Apply a border to the div that has been selected

I have a tool for storing information and I am using *ngFor to display each instance in a line. Is there a way to add a border when clicking on a line? The border should only appear on the clicked line, disappearing from the previous one if another line i ...

Develop a universal function for inserting information into an array within a data set

I need assistance with my Typescript code. I am currently working on a method that pushes data into an array in a mongoose collection. However, the issue I'm facing is that the value is not being passed dynamically to the Key field in the $set operato ...

Module '‘@typescript-eslint/utils/ts-eslint’' or its type declarations could not be located

Currently, I am involved in a Nodejs typescript project where I am looking to establish a connection with a database. To achieve this, I have incorporated the packages mysql2 and sequelize. However, upon adding these packages, I encountered two specific er ...

Typescript not being transpiled by Webpack

As I set out to create a basic website, I opted to utilize webpack for packaging. TypeScript and SASS were my choice of tools due to their familiarity from daily use. Following the documentation at https://webpack.js.org, I encountered issues with loaders ...

I encountered an error while working with Node.js where it stated that `readFileSync` is

In my current web app project, I am utilizing Angular 2, typescript, Node.js, and Visual Studio. The task at hand is to access a file stored in the wwwroot folder. To accomplish this, I have devised the following class: ///<reference path="../../node_m ...

Typescript's Array extension causes issues with constructors

During my experimentation with TypeScript, I encountered an interesting behavior: class ExtArray<U> extends Array<U> { constructor(...args : U[]) { super(...args); } public contains(element : U) : boolean { var i ...

What is the reason for calling Proxy on nested elements?

Trying to organize Cypress methods into a helper object using getters. The idea is to use it like this: todoApp.todoPage.todoApp.main.rows.row .first().should('have.text', 'Pay electric bill'); todoApp.todoPage.todoApp.main.rows.ro ...

An issue has occurred: The function _co.deleteConsulta is not recognized as a valid function

While following a tutorial on creating a CRUD application using Firestore, I encountered an issue when trying to delete data from Firestore. Every time I attempt to delete a user from my Firestore database, I receive an error stating that ._co.deleteConsul ...

There seems to be a console error in Angular 5 when using IE 11

I've developed an Angular 4 application using MVC and Visual Studio 2015. Everything runs smoothly when I access the application on Chrome, but I encounter the following exception on IE 11: XHR error: (404 Not Found) loading http://localhost/src/ma ...

Extraction of properties from an object in an ES6 class

How should object destructuring be properly applied for methods within ES6 classes? user.ts import { Request, Response } from "express"; export class User { constructor (){ Object.assign(this,{ root:this.root, get:this.get ...

Exploring the Usage of Jasmine Testing for Subscribing to Observable Service in Angular's OnInit

Currently, I am facing challenges testing a component that contains a subscription within the ngOnInit method. While everything runs smoothly in the actual application environment, testing fails because the subscription object is not accessible. I have att ...

Utilizing HTML across various components

Can one component's HTML be utilized in another component? If I opt for Selector, it will come with the TS Component functionality as well. Is there a way to reuse that specific HTML page in multiple locations? ...