注释动态加载的TypeScript抽象类的最佳方法是什么?

时间:2019-09-06 06:54:41

标签: javascript typescript types abstract-class

我正在编写一个库,该库可让您插入外部实现,并且正在尝试找出为这些类型编写类型的最佳方法。

示例

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();
    }
}

我想公开一些易于使用的内置类,或者用户可以带来自己的类。我该怎么做?

const zoo = new ZooClient('dog');
// or
const zoo = new ZooClient(new Dolphin()); // Or perhaps `new ZooClient(Dolphin)`?

我正在专门研究一种为ZooClient的用户提供不错的选择的巧妙方法-类型信息应让他们知道他们可以使用字符串(BuiltinAnimal)或类那是他们自己实现的Animal

1 个答案:

答案 0 :(得分:1)

顺便说一句,现在您的CatDog类型是structurally相同,这意味着编译器无法分辨它们之间的区别。这不一定是问题,但确实会导致一些令人惊讶的结果(例如,IntelliSense可能会报告Dog的类型为Cat)。例如,我通常喜欢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");
  }
}

现在CatDog在结构上有所不同(一个可以chaseMice(),另一个可以chaseCars())以及名义上的(不同的名称),并且都正确世界。


因此,我建议创建一个内置Animal内置构造函数的键控注册表:

const builtInAnimals = {
  cat: Cat,
  dog: Dog
};

和关联的类型:

type BuiltInAnimals = typeof builtInAnimals;

然后您可以使ZooClient类的工作方式如下:

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")或返回某个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'.

那些被正确拒绝了。


好的,希望能有所帮助;祝你好运!

Link to code