【问题标题】:What's the best way to annotate dynamically loaded TypeScript abstract classes?注释动态加载的 TypeScript 抽象类的最佳方法是什么?
【发布时间】:2019-09-06 06:54:41
【问题描述】:

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

示例

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 的实现。

【问题讨论】:

  • ZooClient 的实例实际上应该做什么? “加载”DogCat 是什么意思?
  • 我添加了更多代码来帮助更好地解释我的用例。
  • 所以ZooClient 不需要记住它拥有什么类型的动物?只是Animal?那么如果DogchaseCars()方法,CatchaseMice()方法,就不需要new ZooClient('dog').mostFamousAnimal.chaseCars()编译了?
  • 你当前的签名有AnimalLike,更像是{"dolphin": Dolphin}作为参数。但是,是的,new ZooClient(Dolphin) 会更有意义。
  • @jcalz CatDog 都扩展了 Animal,我只对使用 makeSounds() 感兴趣,Animal 的每个实现都会定义它。我正在寻找一种巧妙的方法,能够为ZooClient 的用户提供不错的选择 - 类型信息应该让他们知道他们可以使用字符串 (BuiltinAnimal) 或他们自己实现 @ 的类987654347@.

标签: javascript typescript types abstract-class


【解决方案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

【讨论】:

  • 太棒了!这很有效,感谢您提供详细的演练和示例!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-07-11
  • 2021-08-31
  • 1970-01-01
  • 2023-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多