用户定义的类型保护和lodash

时间:2017-02-18 07:53:05

标签: typescript lodash typescript-typings

我一直在玩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[];
}

这甚至可能吗?我觉得这会尝试将多个类型的警卫聚合成一个函数。从概念上讲,这些类型是有意义的,但我不确定如何实现这一目标。

1 个答案:

答案 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 typesmodule 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[];
}

看起来好多了。