Issue
In my current typescript project I'm trying to create a mixin so that I can create multiple child classes that inherit from different base classes.
It all works well, but I can't seem to figure out how to tell typescript that the new derived class has different arguments than the base class. Here's an example that illustrates what I'm trying to do here
interface ConstructorFoo {
bar: string,
}
class Foo {
public bar: string
constructor({ bar }: ConstructorFoo) {
this.bar = bar
}
}
interface ConstructorBaz extends ConstructorFoo {
qux: string
}
type FooType = new (...args: any[]) => Foo
const quxMixin = <T extends FooType>(base: T) => {
return class Baz extends base {
public qux: string
constructor (...args: any[]) {
super(...args)
const { qux } = args[0] as ConstructorBaz
this.qux = qux
}
}
}
const FooBaz = quxMixin(Foo)
const q = new FooBaz({
bar: '1',
qux: '2' // Argument of type '{ bar: string; qux: string; }' is not assignable to parameter of type 'ConstructorFoo'.
// Object literal may only specify known properties, and 'qux' does not exist in type 'ConstructorFoo'.
})
But I get the following error as I don't know how to specify class Baz
has different argument types:
Argument of type '{ bar: string; qux: string; }' is not assignable to parameter of type 'ConstructorFoo'.
Object literal may only specify known properties, and 'qux' does not exist in type 'ConstructorFoo'.
Thanks for your help and here's a playground link detailing exactly what I want to do
Solution
Try this:
/** A constructor that constructs a T using the arguments A */
type Constructor<T = any, A extends any[] = any[]> = new (...args: A) => T
/** Exclude the first element of an array */
type Tail<T extends any[]> = T extends [any, ...infer U] ? U : never
interface Qux {
qux: string
}
/** Add the Qux type to the first item in an array */
// If the T is empty, T[0] will be never and T[0] & Qux will also be never, so
// this needs to check if the array is empty
type AddQux<T extends any[]> = T extends [] ? [Qux] : [T[0] & Qux, ...Tail<T>]
// quxMixin accepts a constructor base and returns another constructor
const quxMixin = <T extends Constructor>(base: T): Constructor<
// that constructs the original class with the qux property
InstanceType<T> & Qux,
// using the same arguments as the original constructor except that the first
// parameter includes the qux property
AddQux<ConstructorParameters<T>>
> => {
return class Baz extends base {
public qux: string
constructor (...args: any[]) {
super(...args)
const { qux } = args[0] as Qux
this.qux = qux
}
}
}
const FooBaz = quxMixin(Foo)
const q = new FooBaz({ bar: '1', qux: '2' })
q.qux // string
This uses the utility types InstanceType
and ConstructorParameters
:
/** Obtain the return type of a constructor function type */
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any
/** Obtain the parameters of a constructor function type in a tuple */
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never
Answered By - cherryblossom
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.