Använder TypeScripts InstanceType-generics med abstrakta klasser?
TypeScript 2.8 lade till en ny kärntyp InstanceType som kan användas för att få returtypen av en konstruktörsfunktion.
/**
* 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;
Den här funktionen är ganska bra, men fallerar när man använder abstrakta klasser, som inte har en new-deklaration enligt TypeScripts typ-system.
Först trodde jag att jag kunde kringgå denna begränsning genom att skapa en liknande men mindre restriktiv typ (genom att ta bort extends new (...args: any[]) => any-skyddet):
export type InstanceofType<T> = T extends new(...args: any[]) => infer R ? R : any;
Men det faller också isär när det passeras en abstrakt klass, eftersom det inte kan härleda returtypen och standardinställs till any. Här är ett exempel som använder en mock-DOM som exempel, med försök till typomvandling.
abstract class DOMNode extends Object {
public static readonly TYPE: string;
constructor() { super(); }
public get type() {
return (this.constructor as typeof DOMNode).TYPE;
}
}
class DOMText extends DOMNode {
public static readonly TYPE = 'text';
constructor() { super(); }
}
abstract class DOMElement extends DOMNode {
public static readonly TYPE = 'text';
public static readonly TAGNAME: string;
constructor() { super(); }
public get tagname() {
return (this.constructor as typeof DOMElement).TAGNAME;
}
}
class DOMElementDiv extends DOMElement {
public static readonly TAGNAME = 'div';
constructor() { super(); }
}
class DOMElementCanvas extends DOMElement {
public static readonly TAGNAME = 'canvas';
constructor() { super(); }
}
// Create a collection, which also discards specific types.
const nodes = [
new DOMElementCanvas(),
new DOMText(),
new DOMElementDiv(),
new DOMText()
];
function castNode<C extends typeof DOMNode>(instance: DOMNode, Constructor: C): InstanceofType<C> | null {
if (instance.type !== Constructor.TYPE) {
return null;
}
return instance as InstanceofType<C>;
}
// Attempt to cast the first one to an element or null.
// This gets a type of any:
const element = castNode(nodes[0], DOMElement);
console.log(element);
Finns det något sätt jag kan tycka att en variabel är en instans av den konstruktor som passerats, om den konstruktor är en abstrakt klass?
OBS: Jag försöker undvika att använda instanceof eftersom JavaScripts instanceof är mycket problematiskt (2 olika versioner av samma modul har olika konstruktörsinstanser).
Svar #1
Du kan fråga typen av prototype för en abstrakt class för att få typen av dess instanser. Detta kräver inte att typen har en new-signatur, endast att den har en prototype-egenskap. Abstrakta klasser har inte en new-signatur men de har en prototype-egenskap.
Här är hur det ser ut
function castNode<C extends typeof DOMNode>(
instance: DOMNode,
Constructor: C
): C['prototype'] | null {
if (instance.type !== Constructor.TYPE) {
return null;
}
return instance;
}
Uttrycket C['P'] i typ-position kallas en indexerad åtkomsttyp. Det är typen för värdet av egenskapen med namnet P i typen C.
