microsoft / microsoft/TypeScript-Website

A better way to use mixins ?

Open
#3,338 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
2.6k
Forks
1.5k
Avg merge
2d 2h
Merged PRs (30d)
7

Description

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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the TypeScript Handbook mixins page linked in the issue and review the supplied Playground example. Assess how the proposed pattern and its stated limitations fit the existing documentation, then update the mixins guidance with an accurate alternative and clear caveats.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
documentation
Issue type
Documentation
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.