Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A better way to use mixins ? #3338

Open
denis-migdal opened this issue Feb 23, 2025 · 0 comments
Open

A better way to use mixins ? #3338

denis-migdal opened this issue Feb 23, 2025 · 0 comments

Comments

@denis-migdal
Copy link

denis-migdal commented Feb 23, 2025

The document gives some examples on how to use mixin here: https://www.typescriptlang.org/docs/handbook/mixins.html

However the 2 proposed solutions has issues:

  • the first one prevent us from having private/protected members, and may generate quite a lot of type deduction error in the function.
  • the second is not that great regarding to type safety.

I think we have a better solution to use mixin, that could be added to the documentation:

class Base {}

// declare the added properties outside of the mixin function
abstract class Mixin {}

class K extends mix(Base, Mixin) { }

Such generic function could be implemented like this:

const MixSrc = Symbol();

type Mix<
            Base  extends          new(...args:any[])=>any,
            Mixin extends abstract new(...args:any[])=>any,
        > = Omit<Base & Mixin, "new"> & (new(...args:ConstructorParameters<Base>) => (InstanceType<Base> & InstanceType<Mixin>))

function mix<
                Base  extends          new(...args:any[]) => any,
                Mixin extends abstract new(...args:any[]) => any
            >(base: Base, mixin: Mixin): Mix<Base, Mixin> {

    class _ extends base {}

    const static_props = Object.getOwnPropertyDescriptors(mixin);
    delete static_props.prototype;
    delete static_props.name;
    Object.defineProperties( _, static_props );

    ((_ as any)[MixSrc] ??= []).push(mixin);

    const hasInstance = mixin[Symbol.hasInstance];
    Object.defineProperty(mixin, Symbol.hasInstance, {
        value: function (instance: any) {
            if( instance.constructor[MixSrc].includes(this) )
                return true;
            return hasInstance.call(this, instance);
        },
        writable: false,
    });

    const instance_props = Object.getOwnPropertyDescriptors(mixin.prototype);
    // @ts-ignore
    delete instance_props.constructor;
    Object.defineProperties( _.prototype, instance_props );

    return _ as any; // well...
}

Playground Link

There are 2 limitations of this method:

  • This may not work if Mixin inherit from another class ( though you could just call mix for each of the Mixin bases ).
  • This will not work if Mixin has private # properties.

If we have private properties, we can do something like:

abstract class Mixin {
     // declare the public/protected interface here
}

type ExcludeProtected<T> = {[K in keyof T]: T[K]}
function addMixin<...>(base): Mix<Base, Mixin> {
      return class _ extends base implements ExcludeProtected<Mixin> {
             // implements Mixin methods here...
             // can't guarantee the protected interface (due to TS limitations).
      } as any;
}

But then Mixin must not inherit from another class.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant