【发布时间】:2021-02-21 19:09:18
【问题描述】:
我正在尝试过滤数组并自动推断返回类型。
enum Category {
Fruit,
Animal,
Drink,
}
interface IApple {
category: Category.Fruit
taste: string
}
interface ICat {
category: Category.Animal
name: string
}
interface ICocktail {
category: Category.Drink
price: number
}
type IItem = IApple | ICat | ICocktail
const items: IItem[] = [
{ category: Category.Drink, price: 30 },
{ category: Category.Animal, name: 'Fluffy' },
{ category: Category.Fruit, taste: 'sour' },
]
所以现在我想过滤items,类似于:
// return type is IItem[], but I want it to be IFruit[]
items.filter(x => x.category === Category.Fruit)
我知道Array#filter 太笼统了,无法做到这一点,所以我尝试将其包装在自定义函数中:
const myFilter = (input, type) => {
return input.filter(x => x.category === type)
}
所以,我只需要添加类型就可以了。让我们试试吧:
第一个想法是添加返回条件类型:
const myFilter = <X extends IItem, T extends X['category']>(
input: X[],
type: T
): T extends Category.Fruit ? IApple[] : T extends Category.Drink ? ICocktail[] : ICat[] => {
// TS error here
return input.filter((x) => x.category === type)
}
虽然myFilter 的返回类型现在确实运行良好,但存在两个问题:
-
input.filter((x) => x.category === type)突出显示为错误:Type 'X[]' is not assignable to type 'T extends Category.Fruit ? IApple[] : T extends Category.Drink ? ICocktail[] : ICat[]' - 我手动指定了所有可能的情况,基本上是在做编译器应该做的工作。当我只有 3 个类型的交叉点时很容易做到,但是当有 20 个时……就不是那么容易了。
第二个想法是添加某种约束,如下所示:
const myFilter = <X extends IItem, T extends X['category'], R extends ...>(input: X[], type: T): X[] => {
return input.filter(x => x.category === type)
}
但是什么 R extends?我没有。
第三个想法是使用重载,但这也不是一个好主意,因为它需要手动指定所有类型,就像在想法 #1 中一样。
在现代 TS 中是否有可能只使用编译器来解决这个问题?
【问题讨论】:
标签: typescript discriminated-union