I am using a custom library, and for some types I have to write:
import * as pipeline from 'custom-pipeline';
import {MapTransform} from 'custom-pipeline';
export const createTransform = (appConfig: IAppConfig):
MapTransform<ISomeData, IFoo> |
MapTransform<ISomeData, IBar> => {
switch(appConfig.createType) {
case 'foo':
return new pipeline.MapTransform<ISomeData, IFoo>((data: ISomeData) => {...});
case 'bar':
return new pipeline.MapTransform<ISomeData, IBar>((data: ISomeData) => {...});
}
}
Especially the verbose constructor is irking me. I can I alias the types alright:
type FooTransform = MapTransform<ISomeData, IFoo>;
type BarTransform = MapTransform<ISomeData, IBar>;
Yet I cannot do:
new FooTransform((data: ISomeData) => {...});
new BarTransform((data: ISomeData) => {...});
throwing an error like:
error TS2304: Cannot find name 'FooTransform'.
I assume it's because I only have a type and not a class? Yet how can I alias the constructor in a way that I can do new FooTransform
as above?
The definition of MapTransform
looks like:
export declare class MapTransform<A, B> extends BaseTransform<A, B> {
constructor(private mapFunc: (val: A) => B);
}
I can reduce the constructor to this:
fooMapFunction = (data: ISomeData): IFoo => {...};
new MapTransform<ISomeData, IFoo>(mapFunction);
Albeit it's not en par with a new FooTransform(fooMapFunction)
.
In Typescript, Type aliases give a type a new name. They are similar to interfaces in that they can be used to name primitives and any other kinds that you'd have to define by hand otherwise. Aliasing doesn't truly create a new type; instead, it gives that type a new name.
The TypeScript docs have a great example of constructor usage: class Greeter { greeting: string; constructor(message: string) { this. greeting = message; } greet() { return "Hello, " + this. greeting; } } let greeter = new Greeter("world");
TypeScript defines a constructor using the constructor keyword. A constructor is a function and hence can be parameterized. The this keyword refers to the current instance of the class. Here, the parameter name and the name of the class's field are the same.
Use the typeof operator to check the type of a variable in TypeScript, e.g. if (typeof myVar === 'string') {} . The typeof operator returns a string that indicates the type of the value and can be used as a type guard in TypeScript. Copied!
Your assumption is correct, type declarations are compile-time only, thus you can't perform any operations on them (including instantiation with new
).
Let's assume the superclass looks like this:
class MapTransform<T1> {
constructor(public readonly data: T1) { /* ... */ }
}
To create a specialised type alias, you can do this:
type TransformA = MapTransform<number>;
From the perspective of the superclass MapTransform
, there's no difference between MapTransform<A>
, MapTransform<B>
and so on (that's the point of generics), so we can safely assign the constructor function of class MapTransform
to a constant TransformA
. Calling new TransformA()
is, at runtime, identical to calling new MapTransform<number>()
:
const TransformA = <{ new (data: number): TransformA; }>MapTransform;
Notice the type assertion? This tells the TypeScript compiler to treat the assigned value MapTransform
as an object which constructs an object of type TransformA
when instantiating it with new
. We can now write:
const a = new TransformA(123);
a.data.toExponential(); // works
BTW, what the TypeScript compiler actually sees is this ...
const TransformA = <{ new (data: number): MapTransform<number>; }>MapTransform;
... because type TransformA ≡ MapTransform<number>
.
Be aware that all of these will evaluate true
:
new TransformA(123) instanceof TransformA
new TransformA(123) instanceof MapTransform
new MapTransform<number>(123) instanceof TransformA
new MapTransform<number>(123) instanceof MapTransform
Here's the example in the TypeScript Playground.
Again, let's assume the superclass looks like this:
class MapTransform<T1> {
constructor(public readonly data: T1) { /* ... */ }
}
With subclassing, the solution is simple: Extend the superclass and pass the desired type parameters along in the extends clause.
class TransformA extends MapTransform<number> { }
Et voilà, you now have a constructor that works at runtime as well as a type that works at compile-time.
Unlike the first solution, the following 2 expressions will evaluate true
...
new TransformA(123) instanceof TransformA
new TransformA(123) instanceof MapTransform
... while these will evaluate false
:
new MapTransform<number>(123) instanceof TransformA
new MapTransform<number>(123) instanceof MapTransform
If you only need the constructor alias but not the type alias, this might come in handy:
const TransformA = class extends MapTransform<number> { };
This is called a class expression and can be used like every other expression, for example:
class App {
private static TransformA = class extends MapTransform<number> { };
public doStuff() {
return new App.TransformA(123);
}
}
If you're interested, here's a few more links on the topic:
You wrote you've had issues applying these solutions to a class that expects a function as a parameter, so here's another example in the TypeScript playground.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With