DEV Community

Savchenko Alexander
Savchenko Alexander

Posted on

Mixins with generics in Typescript

I spend 10h trying to find a solution!
I hope it will help someone

Problem

I have a class with generic type. After wrapping it with a mixin I lost the possibility to define the type

type Data = { id: string } & Record<string, any>

class Store<TData extends Data> {
  constructor(data: TData) {
    super(idKey)
    this.data = data
  }

  getData(): TData {
    return this.data
  }
}
Enter fullscreen mode Exit fullscreen mode

This is how I wrote my mixin at first

type AnyConstructor<A = object> = new (...input: any[]) => A

const Updatable = <T extends AnyConstructor<Store<Data>>>(
  base: T
) => {
  // @ts-expect-error https://github.com/microsoft/TypeScript/issues/37142
  return class StoreUpdatable<TData extends Data> extends base {
    private _listeners: ((arg: this) => void)[]

    constructor(data: StoreData<TData>) {
      super(data)
      this._listeners = []
    }

    update() {
      this._listeners.forEach((fn) => fn(this))
    }
    addUpdateListener(fn: (arg: this) => void) {
      this._listeners.push(fn)
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

As you can see I mistakenly define type Item at the initialization but I need somehow to pass generic

Solution

const Updatable = <TData extends Data, T extends AnyConstructor<Store<TData>>>(
  base: T
) => {
  // @ts-expect-error https://github.com/microsoft/TypeScript/issues/37142
  class StoreUpdatable extends base {
    private _listeners: ((arg: this) => void)[]

    constructor(data: StoreData<TData>) {
      super(data)
      this._listeners = []
    }

    update() {
      this._listeners.forEach((fn) => fn(this))
    }
    addUpdateListener(fn: (arg: this) => void) {
      this._listeners.push(fn)
    }
  }

  return StoreUpdatable as AnyConstructor<StoreUpdatable> 
   & typeof StoreUpdatable & T
}
Enter fullscreen mode Exit fullscreen mode

Discussion (1)

Collapse
ecyrbe profile image
ecyrbe • Edited on

Nice one, i use similar approach for zodios, here to automatically generate alias methods.

I did not need a mixin, but generate dynamic typescript signature based on constructor parameters. It's almost like mixins, but the code is generated by my class instead.