在TypeScript接口中使用字符串枚举值作为计算属性键

5
我希望定义一个函数,根据给定的键返回不同类型的对象。这基本上就像在createElement函数中使用的技巧。

https://github.com/Microsoft/TypeScript/blob/master/lib/lib.dom.d.ts#L3117

然而,我不想使用字符串字面量,而是想使用字符串枚举类型。因此我编写了类似以下的代码:

class Dog {}
class Cat {}
class Bird {}

enum Kind {
  Dog = 'Dog',
  Cat = 'Cat',
  Bird = 'Bird'
}

interface KindMap {
  [Kind.Dog]: Dog
  [Kind.Cat]: Cat
  [Kind.Bird]: Bird
}

function getAnimal<K extends keyof KindMap> (key: K): KindMap[K] {
  switch (key) {
    case Kind.Dog:
      return new Dog()
    case Kind.Cat:
      return new Cat()
    case Kind.Bird:
      return new Bird()
  }
}

然而,TypeScript似乎不喜欢我将enum Kind的值放在接口中作为计算属性的方式,它会抱怨。
A computed property name in an interface must directly refer to a built-in symbol.

这里出现了问题,我已经在枚举中定义了常量,但是我不想使用字符串字面量,有没有办法让这个工作起来?也就是说,可以使用 Kind 枚举的值作为计算属性键值在 KindMap 中工作。

3
在当前的 TypeScript 版本中不支持这个,看起来可能会在 2.7 版本中修复。 - artem
2个回答

6
有时,使用 TypeScript 中的 keyof 来表示一个简单对象比使用枚举更简单:
class Dog { }
class Cat { }

const kindMap = {
    Dog,
    Cat
};

type KindMap = typeof kindMap;

function getAnimalClass<K extends keyof KindMap>(key: K): KindMap[K] {
    return kindMap[key];
}

// These types are inferred correctly
const DogClass = getAnimalClass('Dog'); 
const dog = new DogClass();

const CatClass = getAnimalClass('Cat');
const cat = new CatClass();

在TypeScript Playground中尝试

我试图使用映射类型实现getAnimal(),但似乎遇到了推断错误。不过查找类更容易。

getAnimalClass查找内联也适用于类型推断:

const dog = new kindMap['Dog']();

我先在https://stackoverflow.com/a/64740836/470749找到了答案,然后来到这里,并且非常感谢你提供的计算字符串枚举的解决方法。 - undefined

1
原始代码在TypeScript 2.7.2中几乎可以直接使用,但是会有一个错误,因为除了三个Kind之外的值可能会被传递进来。
你可以说它有可能返回undefined:
function getAnimal<K extends keyof KindMap>(key: K): undefined | KindMap[K] {
    switch (key) {
        case Kind.Dog:
            return new Dog()
        case Kind.Cat:
            return new Cat()
        case Kind.Bird:
            return new Bird()
        default:
            return undefined;
    }
}

我可能会直接使用枚举到类构造函数的映射,因为使用它可以轻松地推断出类型:

const kindMap = {
    [Kind.Dog]: Dog,
    [Kind.Cat]: Cat,
    [Kind.Bird]: Bird,
}

const tweeter = new kindMap[Kind.Bird]();

如果您希望函数实例化对象,您也可以设置并行接口:
interface KindMap {
    [Kind.Dog]: Dog,
    [Kind.Cat]: Cat,
    [Kind.Bird]: Bird,
}

const getAnimal = <K extends Kind>(k: K): KindMap[K] => new kindMap[k]();

const meower = getAnimal(Kind.Cat);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接