我一直在玩lodash和打字稿,发现了以下内容。
假设您有一个用户定义的类型保护,它具有以下签名:
isCat(animal: Animal) animal is Cat
你有一个你想过滤的动物清单:
let animals: Animal[] = // assume some input here
let cats = _.filter(animals, isCat);
然后类型系统实际上会推断出猫类型为Animal [],而不是Cat []类型。
但是如果你像这样扩展lodash类型(对不起,我只是巧合地使用链接,但你明白了):
interface TypeGuardListIterator<T, TResult extends T> {
(value: T, index: number, list: List<T>): value is TResult;
}
interface _Chain<T> {
filter<TResult extends T>(iterator: TypeGuardListIterator<T, TResult>): _Chain<TResult>;
}
然后类型系统实际上会推断出cat变量的类型为Cat []。这太棒了!也许它应该被添加到这个库的类型中。
以下是一个问题:假设您有多种类型的动物,你怎么能用一个小组来做这个,并且类型推断也能正常工作?
let groupedAnimals = _.groupBy(animals, animal => {
if (isCat(animal)) {
return "cats";
} else if (isDog(animal)) {
return "dogs";
} else if (isHorse(animal)) {
return "horses";
}
});
理想情况下,groupsAnimals的类型如下所示:
interface GroupedAnimals {
cats: Cat[];
dogs: Dog[];
horses: Horse[];
}
这甚至可能吗?我觉得这会尝试将多个类型的警卫聚合成一个函数。从概念上讲,这些类型是有意义的,但我不确定如何实现这一目标。
答案 0 :(得分:0)
您不能为此使用类型防护,但是还有其他方法可以在类型级别上区分类型。
要使其成为可能,您将必须用一个签名来增强groupBy,该签名“理解”您试图通过类型级别的返回值来区分联合成员。
此术语的通用术语是工会歧视,而在工会成员之间进行区分的最常见方法是通过静态标记成员,该成员可以在类型级别以及运行时进行区分。 This post详细说明了标记工会和工会歧视的概念。
为简洁起见,省略了Horse
类型,以下是您的情况:
import {groupBy} from "lodash";
interface Cat {
_type: "cat"
}
interface Dog {
_type: "dog"
}
type Animal = Cat | Dog;
const animals: Animal[] = [];
declare module "lodash" {
interface LoDashStatic {
groupBy<T extends Animal>(collection: List<T>, iteratee?: (i: T) => T["_type"]): {
[K in T["_type"]]: Array<T & {_type: K}>
};
}
}
// Use groupBy
const group = groupBy(animals, (animal) => animal._type);
如果上述代码没有意义,则可能需要阅读有关mapped types和module augmentation的更多信息。
推断出的组类型为:
const group: {
cat: ((Cat & {
_type: "cat";
}) | (Dog & {
_type: "cat";
}))[];
dog: ((Cat & {
_type: "dog";
}) | (Dog & {
_type: "dog";
}))[];
}
这实际上是您想要的(因为Dog & {_type: "cat"}
和Cat & {_type: "dog"}
永远不会匹配任何内容),但是看起来很难看。
要清理一点,可以使用鉴别器界面:
interface AnimalDiscriminator {
cat: Cat,
dog: Dog
}
您可以在您的groupBy
签名中进行映射:
declare module "lodash" {
interface LoDashStatic {
groupBy<T extends Animal>(collection: List<T>, iteratee?: (i: T) => T["_type"]): {
[K in T["_type"]]: Array<AnimalDiscriminator[K]>
};
}
}
现在组的类型将是:
const group: {
cat: Cat[];
dog: Dog[];
}
看起来好多了。