在接口上定义索引类型的选择

时间:2019-02-18 19:08:34

标签: javascript typescript

让我们说我有一个Animal接口,我希望它具有一些常规属性,然后成为猫或狗并具有相应的属性。

interface Dog {
    dog: { sound: string; }
}

interface Cat {
    cat: { lives: number; }
}

type CatOrDog = Cat | Dog;

interface Animal {
    weight: number;
    // index type of CatOrDog
}

所以我在想

interface Animal {
   weight: number;
   [K in keyof CatOrDog]: CatOrDog[K];
}

但是当我使用[K:string]: type

以外的任何东西时,TypeScript都会非常生气。

我想要实现的是

// Success
const dog = <Animal> {
    weight: 5,
    dog: {sound: "woof" }
}

// Error, lives doesn't exist on Dog
const errorAnimal = <Animal> {
    weight: 5,
    dog: {sound: "woof" },
    cat: { lives: 9 }
}

此外,如果我想添加更多的索引类型,那有可能吗?

2 个答案:

答案 0 :(得分:1)

诸如Cat | Dog之类的联盟为inclusive,这意味着某事物如果是Cat | DogCat或两者都是Dog 。 TypeScript没有通用的exclusive union运算符。如果您的工会拥有具有不同值的共同财产,则可以使用@MateuszKocz建议的有区别的工会。否则,您可以build your own Xor为对象键入函数:

type ProhibitKeys<K extends keyof any> = { [P in K]?: never }

type Xor<T, U> = (T & ProhibitKeys<Exclude<keyof U, keyof T>>) |
  (U & ProhibitKeys<Exclude<keyof T, keyof U>>);

然后,您可以将Animal定义为CatDogintersected的排他联合,并具有所有Animal共有的附加属性:

type Animal = Xor<Cat, Dog> & { weight: number };

现在您可以获得所需的行为(类型注释优于类型断言,因此我在这里使用它们):

// Success
const dog: Animal = {
  weight: 5,
  dog: { sound: "woof" }
}

// Error, {lives: number} not assignable to undefined
const errorAnimal: Animal = {
  weight: 5,
  dog: { sound: "woof" },
  cat: { lives: 9 }
}

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

答案 1 :(得分:0)

如果您愿意稍微更改代码,那么tagged unions将是您想要的答案。

interface CommonAnimal {
  weight: number
}

interface Dog extends CommonAnimal {
  // This is the important part. `type` a tag used by TS to recognise this type.
  type: 'dog'
  sound: string
}

interface Cat extends CommonAnimal {
  type: 'cat'
  lives: number
}

type Animal = Dog | Cat

const dog: Animal = {
  type: 'dog',
  weight: 10,
  sound: 'woof'
}

const cat: Animal = {
  type: 'cat',
  weight: 5,
  lives: 9
}

const robot: Animal = {
  type: 'robot' // error
}

这样,您就可以在满足TS的类型识别的同时,将值保持在一个级别而无需嵌套。