Last active
September 28, 2019 23:09
-
-
Save masonmark/6372ef09074af82bb9478442b2e0eff3 to your computer and use it in GitHub Desktop.
How to reference a subclass type in an inheritied static method in TypeScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Mason 2019-09-29: This is one way to allow subclasses to inherit a | |
* method with a parameter whose type refers to the actual subclass type | |
* (and not the parent class's type). | |
* | |
* One purpose here is to define a base class, that can have various | |
* subclasses, and enable all of the subclasses to inherit a class method | |
* named configure(), which creates an instance of the subclass and then | |
* configures the instance and its various subclass-specific properties. | |
* | |
* This gets weird in TypeScript, due to polymorphic 'this' not being | |
* available for static methods and constructors. This implementation was | |
* derived from the comment thread at: | |
* | |
* https://github.com/microsoft/TypeScript/issues/5863 | |
* | |
* (That thread also shows several of the ways that trying to do this | |
* kind of thing can fail to work in TypeScript, which may be surprising.) | |
* | |
* This implementation uses the "fake this parameter" described here: | |
* | |
* https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#specifying-the-type-of-this-for-functions | |
* | |
* Combined with `InstanceType<T>` this allows us to capture the type in | |
* such a way that the configurator function works in subclasses, and the | |
* subclass properties can be auto-completed, etc. | |
*/ | |
class Configurable { | |
static configure<T extends typeof Configurable>(this: T, configurator: ((x: InstanceType<T>) => void)): InstanceType<T> { | |
const result = (new this()) as InstanceType<T>; | |
configurator(result); | |
return result; | |
} | |
} | |
class Person extends Configurable { | |
name = "Alice"; | |
age = 0; | |
get bio() { | |
return `My name is ${this.name}, and I am a ${this.constructor.name}. I am ${this.age} years old.`; | |
} | |
} | |
class FireFighter extends Person { | |
helmetSize?: number; | |
hasLicenseToDriveFireEngine = false; | |
} | |
class PoliceOfficer extends Person { | |
badgeNumber = "0000000"; | |
} | |
const rand = Person.configure( x => { | |
x.age = 117; | |
x.name = "Rand"; | |
}); | |
const jane = PoliceOfficer.configure( x => { | |
x.name = "Jane"; | |
x.age = 42; | |
x.badgeNumber = "8675309"; | |
}); | |
const configureBiff = (x: FireFighter) => { | |
x.hasLicenseToDriveFireEngine = true; | |
x.helmetSize = 15; | |
x.age = 21; | |
x.name = "Biff Wigginberger Jr."; | |
}; | |
const biff = FireFighter.configure(configureBiff); | |
console.log(rand.bio); | |
console.log(jane.bio); | |
console.log(biff.bio); | |
// prints: | |
// My name is Rand, and I am a Person. I am 117 years old. | |
// My name is Jane, and I am a PoliceOfficer. I am 42 years old. | |
// My name is Biff Wigginberger Jr., and I am a FireFighter. I am 21 years old. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment