如何在Typescript中为执行不太具体的函数的更具体的函数设置类型?

kpbpu008  于 2023-02-10  发布在  TypeScript
关注(0)|答案(1)|浏览(146)

为什么我会得到一个错误TS2322?如何处理这种情况来摆脱它?
事实上,“makeAnimal”函数不太具体,而“makeCat”更具体,据我所知,Typescript告诉我,我不能为更具体的函数使用不太具体的输出。
但是我需要一个公共函数和一堆更具体的函数来使用它。
请帮我弥补这一切。

const ANIMAL_CAT = 'cat'
const ANIMAL_DOG = 'dog'
type ICat = 'isCat'
type IDog = 'isDog'
type IAnimal = ICat | IDog
type IMakeAnimal = (type: string) => IAnimal
type IMakeCat = () => ICat
type IMakeDog = () => IDog

const makeAnimal: IMakeAnimal = (type) => {
    switch (type) {
        case ANIMAL_CAT:
            return 'isCat'
        case ANIMAL_DOG:
        default:
            return 'isDog'
    }
}

// TS2322: Type 'IAnimal' is not assignable to type '"isCat"'.
// Type '"isDog"' is not assignable to type '"isCat"'
const makeCat: IMakeCat = () => makeAnimal(ANIMAL_CAT)
b1zrtrql

b1zrtrql1#

您的makeAnimal版本的返回类型只是IAnimal,它可以是ICatIDog。编译器不会尝试模拟如果您调用makeAnimal(ANIMAL_CAT)来进一步缩小返回类型会发生什么。您注解了makeAnimal是一个IMakeAnimal,其返回类型是IAnimal,所以这就是编译器看到的。
如果你想让函数保持原样,那么你必须通过 * 告诉 * 编译器makeAnimal(ANIMAL_CAT)返回的值是ICat来把丢失的信息添加回去,你可以通过类型Assert来完成:

const makeCat: IMakeCat = () =>
  makeAnimal(ANIMAL_CAT) as ICat; // okay
  // ------------------> ^^^^^^^ assert

这是可行的,但它之所以安全,是因为你的Assert碰巧是正确的,编译器无法判断你是否在这里犯了错误;它依赖于你的Assert是否准确。也就是说,编译器不是在验证类型安全,而是你在验证类型安全。举个例子:

const makeCatBad: IMakeCat = () =>
  makeAnimal(ANIMAL_DOG) as ICat; // no errr
// --------------------> ^^^^^^^ assert

这也是没有错误的编译,但是你声称makeAnimal(ANIMAL_DOG)将返回一个ICat,编译器只是相信你。
如果你想要更多的编译器验证的类型安全性,你需要重构makeAnimal(),使它的返回类型依赖于它的输入,比如把它变成一个generic函数...你还需要修改实现,使编译器能够理解你的逻辑匹配泛型返回类型。

interface AnimalMap {
  [ANIMAL_CAT]: ICat,
  [ANIMAL_DOG]: IDog
}

const makeAnimal = <K extends keyof AnimalMap>(type: K): AnimalMap[K] => ({
  [ANIMAL_CAT]: 'isCat' as const,
  [ANIMAL_DOG]: 'isDog' as const
}[type]);

这里我创建了一个AnimalMapMap接口,它表示ANIMAL_CAT/ANIMAL_DOG字符串类型和对应的ICat/IDog输出类型之间的关系,它只是一个接口,因为字符串类型可以用作对象的键类型。
makeAnimal的类型现在是泛型函数,其中type参数是泛型类型K,该泛型类型被约束为keyof AnimalMap,即... ANIMAL_CATANIMAL_DOG。并且makeAnimal的返回类型是indexed access typeAnimalMap[K]。表示键类型为KAnimalMap的属性的值类型。调用签名本质上描述了对象属性的查找。
事实上,我已经将makeAnimal的实现从switch/case语句重构为对象属性查找,编译器对这种实现很满意,因为它认为对象可以赋值给AnimalMap,键type可以赋值给K。因此返回类型被视为可赋值给AnimalMap[K],如果将其保留为switch/case,您会发现编译器无法遵循逻辑,因为K不一定通过检查type而改变。
无论如何,既然makeAnimal()是泛型,返回类型将取决于输入类型,因此makeAnimal(ANIMAL_CAT)将使编译器推断ANIMAL_CATK,并且索引访问类型AnimalMap[ANIMAL_CAT]ICat,所以返回类型是ICat() => makeAnimal(ANIMAL_CAT)可以赋值给IMakeCat,如所期望的:

const makeCat: IMakeCat = () => makeAnimal(ANIMAL_CAT);

Playground代码链接

相关问题