注释动态加载的 TypeScript 摘要 类 的最佳方式是什么?
What's the best way to annotate dynamically loaded TypeScript abstract classes?
我正在编写一个可以让您插入外部实现的库,并且我正在尝试找出为这些编写类型的最佳方法。
例子
abstract class Animal {
public abstract makeSounds();
}
class Dog extends Animal {
public makeSounds() {
console.log('woof');
}
}
class Cat extends Animal {
public makeSounds() {
console.log('meow');
}
}
type BuiltinAnimals = 'cat' | 'dog';
interface AnimalLike {
[name: string]: new () => Animal;
}
default class ZooClient {
public mostFamousAnimal: Animal;
constructor(someAnimal: BuiltinAnimals | AnimalLike) {
if (typeof someAnimal === 'string') {
// if 'dog', load `Dog` and if 'cat', load `Cat`.
// this.mostFamousAnimal = new Cat() or new Dog();
} else {
// load external animal plugin
// this.mostFamousAnimal = new [someAnimal]();
}
}
public makeSounds() {
this.mostFamousAnimal.makeSounds();
}
}
我想公开一些可以随时使用的内置 classes,或者用户可以自带 class。我该怎么做?
const zoo = new ZooClient('dog');
// or
const zoo = new ZooClient(new Dolphin()); // Or perhaps `new ZooClient(Dolphin)`?
我正在寻找一种巧妙的方法,以便能够为 ZooClient
的用户提供不错的选择 - 类型信息应该让他们知道他们可以使用字符串 (BuiltinAnimal
) 或class 是他们自己实现的 Animal
。
顺便说一句,现在你的 Cat
和 Dog
类型是 structurally identical, meaning that the compiler can't tell the difference between them. This isn't necessarily a problem, but it does lead to some surprising results (e.g., IntelliSense might report that a Dog
is of type Cat
). For example code I usually like to avoid such unintentionally equivalent types,所以我会这样做:
class Dog extends Animal {
chaseCars() {}
public makeSounds() {
console.log("woof");
}
}
class Cat extends Animal {
chaseMice() {}
public makeSounds() {
console.log("meow");
}
}
现在 Cat
和 Dog
在结构上不同(一个可以 chaseMice()
而另一个可以 chaseCars()
)以及名义上的(不同的名称)并且都是与世界同在。
因此,我建议创建一个内置 Animal
构造函数的键控注册表:
const builtInAnimals = {
cat: Cat,
dog: Dog
};
和关联类型:
type BuiltInAnimals = typeof builtInAnimals;
然后你可以让你的 ZooClient
class 像这样工作:
class ZooClient {
public mostFamousAnimal: Animal;
constructor(someAnimal: keyof BuiltInAnimals | (new () => Animal)) {
const animalConstructor =
typeof someAnimal === "string" ? builtInAnimals[someAnimal] : someAnimal;
this.mostFamousAnimal = new animalConstructor();
}
public makeSounds() {
this.mostFamousAnimal.makeSounds();
}
}
所以构造函数的输入要么是 keyof BuiltInAnimals
(即本例中的 "cat"
或 "dog"
),要么是 returns 一些 Animal
的构造函数.然后,animalConstructor
局部变量使用 typeof
type guard 来区分 someAnimal
是什么,并且在任何一种情况下都设置为 new() => Animal
类型的东西。然后我们按照您的预期使用该构造函数。
让我们看看它是如何工作的:
const dogZooClient = new ZooClient("dog");
dogZooClient.makeSounds(); // woof
class Dolphin extends Animal {
makeSounds() {
console.log("");
}
}
const dolphinZooClient = new ZooClient(Dolphin);
dolphinZooClient.makeSounds(); //
这就是预期用途,并且有效。让我们确保它没有意外用途:
new ZooClient("badName"); // error!
// Argument of type '"badName"' is not assignable to
// parameter of type '"cat" | "dog" | (new () => Animal)'.
class NotAnAnimal {
makeSmells() {
console.log("");
}
}
new ZooClient(NotAnAnimal); // error!
// Property 'makeSounds' is missing in type 'NotAnAnimal'
// but required in type 'Animal'.
那些被正确地拒绝了。
好的,希望对您有所帮助;祝你好运!
我正在编写一个可以让您插入外部实现的库,并且我正在尝试找出为这些编写类型的最佳方法。
例子
abstract class Animal {
public abstract makeSounds();
}
class Dog extends Animal {
public makeSounds() {
console.log('woof');
}
}
class Cat extends Animal {
public makeSounds() {
console.log('meow');
}
}
type BuiltinAnimals = 'cat' | 'dog';
interface AnimalLike {
[name: string]: new () => Animal;
}
default class ZooClient {
public mostFamousAnimal: Animal;
constructor(someAnimal: BuiltinAnimals | AnimalLike) {
if (typeof someAnimal === 'string') {
// if 'dog', load `Dog` and if 'cat', load `Cat`.
// this.mostFamousAnimal = new Cat() or new Dog();
} else {
// load external animal plugin
// this.mostFamousAnimal = new [someAnimal]();
}
}
public makeSounds() {
this.mostFamousAnimal.makeSounds();
}
}
我想公开一些可以随时使用的内置 classes,或者用户可以自带 class。我该怎么做?
const zoo = new ZooClient('dog');
// or
const zoo = new ZooClient(new Dolphin()); // Or perhaps `new ZooClient(Dolphin)`?
我正在寻找一种巧妙的方法,以便能够为 ZooClient
的用户提供不错的选择 - 类型信息应该让他们知道他们可以使用字符串 (BuiltinAnimal
) 或class 是他们自己实现的 Animal
。
顺便说一句,现在你的 Cat
和 Dog
类型是 structurally identical, meaning that the compiler can't tell the difference between them. This isn't necessarily a problem, but it does lead to some surprising results (e.g., IntelliSense might report that a Dog
is of type Cat
). For example code I usually like to avoid such unintentionally equivalent types,所以我会这样做:
class Dog extends Animal {
chaseCars() {}
public makeSounds() {
console.log("woof");
}
}
class Cat extends Animal {
chaseMice() {}
public makeSounds() {
console.log("meow");
}
}
现在 Cat
和 Dog
在结构上不同(一个可以 chaseMice()
而另一个可以 chaseCars()
)以及名义上的(不同的名称)并且都是与世界同在。
因此,我建议创建一个内置 Animal
构造函数的键控注册表:
const builtInAnimals = {
cat: Cat,
dog: Dog
};
和关联类型:
type BuiltInAnimals = typeof builtInAnimals;
然后你可以让你的 ZooClient
class 像这样工作:
class ZooClient {
public mostFamousAnimal: Animal;
constructor(someAnimal: keyof BuiltInAnimals | (new () => Animal)) {
const animalConstructor =
typeof someAnimal === "string" ? builtInAnimals[someAnimal] : someAnimal;
this.mostFamousAnimal = new animalConstructor();
}
public makeSounds() {
this.mostFamousAnimal.makeSounds();
}
}
所以构造函数的输入要么是 keyof BuiltInAnimals
(即本例中的 "cat"
或 "dog"
),要么是 returns 一些 Animal
的构造函数.然后,animalConstructor
局部变量使用 typeof
type guard 来区分 someAnimal
是什么,并且在任何一种情况下都设置为 new() => Animal
类型的东西。然后我们按照您的预期使用该构造函数。
让我们看看它是如何工作的:
const dogZooClient = new ZooClient("dog");
dogZooClient.makeSounds(); // woof
class Dolphin extends Animal {
makeSounds() {
console.log("");
}
}
const dolphinZooClient = new ZooClient(Dolphin);
dolphinZooClient.makeSounds(); //
这就是预期用途,并且有效。让我们确保它没有意外用途:
new ZooClient("badName"); // error!
// Argument of type '"badName"' is not assignable to
// parameter of type '"cat" | "dog" | (new () => Animal)'.
class NotAnAnimal {
makeSmells() {
console.log("");
}
}
new ZooClient(NotAnAnimal); // error!
// Property 'makeSounds' is missing in type 'NotAnAnimal'
// but required in type 'Animal'.
那些被正确地拒绝了。
好的,希望对您有所帮助;祝你好运!