Given the example TypeScript mixin pattern described here:
type Constructor = new (...args: any[]) => {};
// This mixin adds a scale property, with getters and setters
// for changing it with an encapsulated private property:
function Scale<TBase extends Constructor>(Base: TBase) {
return class Scaling extends Base {
// Mixins may not declare private/protected properties
// however, you can use ES2020 private fields
_scale = 1;
setScale(scale: number) {
this._scale = scale;
}
get scale(): number {
return this._scale;
}
};
}
How would we explicitly annotate the return type of the Scale
function? That is, fill in the ???
:
function Scale<TBase extends Constructor>(Base: TBase): ??? {
...
CodePudding user response:
You don't need to declare explicit return type for Scale
function because TypeScript is smart enought to infer the return type. Hover your mouse on Scale
and you will see that return type is
{
new (...args: any[]): Scaling;
prototype: Scale<any>.Scaling;
} & TBase
Further more, if you want to use explicit return type for Scale
, you should declare Scaling
(the inner class) out of Scale
function. Like this:
class Scaling extends Base {
_scale = 1;
setScale(scale: number) {
this._scale = scale;
}
get scale(): number {
return this._scale;
}
}
function Scale<TBase extends Constructor>(Base: TBase) {
return Scaling
}
But then Base
should be static.
It means that we should create Mixin
function, just like in the docs - Alternative Pattern:
// credits goes to credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;
type ClassType = new (...args: any[]) => any;
function Mixin<T extends ClassType, R extends T[]>(...classRefs: [...R]):
new (...args: any[]) => UnionToIntersection<InstanceType<[...R][number]>> {
return merge(class { }, ...classRefs);
}
function merge(derived: ClassType, ...classRefs: ClassType[]) {
classRefs.forEach(classRef => {
Object.getOwnPropertyNames(classRef.prototype).forEach(name => {
// you can get rid of type casting in this way
const descriptor = Object.getOwnPropertyDescriptor(classRef.prototype, name)
if (name !== 'constructor' && descriptor) {
Object.defineProperty(
derived.prototype,
name,
descriptor
);
}
});
});
return derived;
}
class Foo {
tag = 'foo'
}
class Scaling extends Mixin(Foo) {
_scale = 1;
setScale(scale: number) {
this._scale = scale;
}
get scale(): number {
return this._scale;
}
}
const result = new Scaling();
result.tag // string
result.scale // number
You can find my article in medium and in my blog step-by-step explanation.
UnionToIntersection
- creates intersection of union type. Full explanation you can find in this answer
ClassType
- almost the same type as your Constructor
. It is a type of any class constructor.
Mixin
- infers with help of variadic tuple types each class constructor provided in arguments and merges all their instances into one object UnionToIntersection<InstanceType<[...R][number]>>
.
`[...R][number]` - takes a union of all provided class intstances
`InstanceType<[...R][number]>>` - replace every class constructor in the union
with class instance accordingly
`UnionToIntersection<InstanceType<[...R][number]>>` - merges all class instances
merge
- is the same as applyMixins
from the docs
UPDATE
Since, Scaling
is defined inside a function, it is impossible to add explicit return type which includes Scaling
because it just not exists in the scope yet.