如何在 TypeScript 中将 switch 语句转换为对象字面量

How to convert switch statement to object literal in TypeScript

我正在尝试将 switch 语句转换为打字稿中的对象文字,但出现以下错误:

Argument of type 'AllowedAnimals' is not assignable to parameter of type 'never'.
  The intersection 'Dog & Horse' was reduced to 'never' because property 'species' has conflicting types in some constituents.
    Type 'Dog' is not assignable to type 'never'. 

... 下面是示例代码。我是不是打字不正确,或者这是 Typescript 的反模式?

interface Animal{
    species:string,
    weight:number,
    name:string
}

type AllowedAnimals = Dog |Horse

interface Dog extends Animal{
    species:"dog",
    hasFleas:boolean
}

interface Horse extends Animal{
    species:"horse",
    handsTall:number
}

const processDog = (d:Dog)=>{}

const processHorse = (h:Horse)=>{}

const userInput = "";

const a:AllowedAnimals = JSON.parse(userInput);

switch (a.species){
    case "dog":
        processDog(a);
        break;
    case "horse":
        processHorse(a);
        break;
    default:
        break;
}

const objectLiteral = {
    "dog":processDog,
    "horse":processHorse
}

objectLiteral[a.species](a); //<- Error

正如我在评论中所说:

Typescript 在这方面表现不错。 a 是一个值,您告诉打字稿的类型是 AllowedAnimals ,它本身就是一个联合。当你写 objectLiteral[a.species] 时,打字稿不知道 doghorse 之间的哪一个会被索引,所以它不能给你关于函数参数的提示。

正如您在问题中提到的,您可以受益于 type narrowing 使用 switch 语句来完成您想要做的事情:

interface Animal{
    species:string,
    weight:number,
    name:string
}

type AllowedAnimals = Dog |Horse

interface Dog extends Animal{
    species:"dog",
    hasFleas:boolean
}

interface Horse extends Animal{
    species:"horse",
    handsTall:number
}

const processDog = (d:Dog)=>{}

const processHorse = (h:Horse)=>{}

const process = (animal : AllowedAnimals) => {
    switch(animal.species) {
        case "dog":
            objectLiteral[animal.species](animal);
            break;
        case "horse":
            objectLiteral[animal.species](animal);
            break;
    }
}

const objectLiteral = {
    "dog":processDog,
    "horse":processHorse
}

const userInput = "";

const a:AllowedAnimals = JSON.parse(userInput);

process(a);

Playground.

Typescript 不够聪明,无法确定 objectLiteral[a.species] 的类型取决于 a 的具体类型。它只知道 属性 访问会得到 (d: Dog) => void | (h: Horse) => void,但是当您尝试调用它时,它需要统一参数类型,认为参数需要具有可以是传递给这些函数中的任何一个。 (a: Dog & Horse) => void 显示为 a: never,如错误消息所述。

你可以通过显式转换作弊:

(objectLiteral[a.species] as (a: AllowedAnimal) => void)(a);

这样你就可以满足类型检查器的要求了。我不知道这是否是个好主意——你得到的检查可能不如 switch.

type process<X extends Animal> = (a: X) => void
const processDog: process<Dog> = _=>{}
const processHorse: process<Horse> = _=>{}

const objectLiteral: {
   [p in AllowedAnimals['species']]: process<AllowedAnimals & {species: p}>
} = {
    "dog": processDog,
    "horse": processHorse
};

let a: AllowedAnimals = null as any;
(objectLiteral[a.species] as process<AllowedAnimals>)(a);