我正在TypeScript中创建一个通用的排序模块. 其中一个接收一个数据对象数组,然后一个要排序的键列表,一旦完成数组,将按照键列表中指定的键排序.

首先,我会做这两种类型:

export type Comparer<T>: (a: T, b: T) => number;

export type SortKey<T> = {
    key: keyof T,
    order: 'ascending' | 'descending',
    comparer: Comparer<T[keyof T]>
};

上面的测试结果是有问题的. 让我们看一个例子:

// numberComparer is of type (a: number, b: number) => number
import { numberComparer } from 'somewhere';

type Person = {
    id: number;
    name: string;
};

const sortKey: SortKey<Person> = {
    key: 'id',
    order: 'ascending',
    comparer: numberComparer
};

这显示了一个TS错误:comparer属性是Comparer<string | number>类型,但numberComparer不是.发生这种情况是因为类型无法推断,因为key的值是'id',所以它将比较数字,因为id是数值字段.

所以v2的类型:

export type SortKey<T, K extends keyof T = keyof T> = {
    key: K,
    order: 'ascending' | 'descending',
    comparer: Comparer<T[K]>
};

在此版本中,如果我们显式指定K,则该示例将起作用.就我个人而言,我认为TS应该能够推断出它,但不管怎样:

const sortKey: SortKey<Person, 'id'> = {
    key: 'id',
    order: 'ascending',
    comparer: numberComparer
};

错误现在消失了. 然而,我们面临另一个问题. 一般的sort函数看起来像这样:

export function sort<T>(data: T[], keys: SortKey<T>[]);

编写函数体不会产生错误,因此这适用于函数体. 但是,函数的consumer不能做这样的事情:

const myKeys: SortKey<Person>[] = [
    {
        key: 'id',
        order: 'descending',
        comparer: numberComparer
    },
    {
        key: 'name',
        order: 'ascending',
        comparer: stringComparer
    }
];

上面的错误抛出了最初看到的TS错误,其中给定的比较器是用于特定类型(数字或字符串),但comparer属性接受Person类型中的所有键范围.

一种解决方法是创建一个所需类型的"主"比较器,其实现将对参数进行类型判断,然后相应地调用特定的比较器. 很丑.

那么,我的打字员们,有什么解决办法吗? 一如既往,我感激你.

推荐答案

看起来你希望SortKey<T>SortKey<T, K>中的union,相当于keyof T中的每K. 也就是说,你想要SortKey<T, K>distribute across unionsK. 所以你得到了这个行为:

type SortKeyPerson = SortKey<Person>;
/* type SortKeyPerson = {
    key: "id";
    order: 'ascending' | 'descending';
    comparer: Comparer<number>;
} | {
    key: "name";
    order: 'ascending' | 'descending';
    comparer: Comparer<string>;
} */

这意味着你想要使用一些unions 分配机制.有几种方法可以实现这一点.当您想要分发的类型是Keylike时,最直接的是所谓的distributive object type,就像在microsoft/TypeScript#47109中创造的那样.这涉及对T的属性进行mapped type,然后使用完整的密钥集对其进行indexing into.因此,如果您有一个想要在K中的联合上分发的类型F<K>,您可以编写{[P in K]: F<P>}[K]来生成分发版本.

对于这个例子,它看起来像:

type SortKey<T> = { [K in keyof T]: {
    key: K,
    order: 'ascending' | 'descending',
    comparer: Comparer<T[K]>
} }[keyof T]

您可以验证它为您提供上面所示的联合类型. 现在,您的示例可以按需要工作:

const myKeys: SortKey<Person>[] = [
    {
        key: 'id',
        order: 'descending',
        comparer: numberComparer
    },
    {
        key: 'name',
        order: 'ascending',
        comparer: stringComparer
    }
];

Playground link to code

Typescript相关问答推荐

在TypeScript中使用泛型的灵活返回值类型

如何判断输入是否是TypeScript中的品牌类型?

在Angular中注入多个BrowserModules,但在我的代码中只有一个

有没有办法解决相互影响的路由之间的合并?

使用`renderToPipeableStream`时如何正确恢复服务器端Apollo缓存?

无法绑定ngModel.条件ngSwitchCase中的错误

状态更新后未触发特定元素的Reaction CSS转换

获取函数中具有动态泛型的函数的参数

如何在TypeScrip中正确键入元素和事件目标?

等待用户在Angular 函数中输入

有没有可能产生输出T,它在视觉上省略了T中的一些键?

如何将TS泛型用于react-hook-form接口?

RXJS将对键盘/鼠标点击组合做出react

如何在Angular 服务中制作同步方法?

打字脚本中的模块化或命名空间

在Thunk中间件中输入额外参数时Redux工具包的打字脚本错误

在Google授权后由客户端接收令牌

如何创建允许使用带有联合类型参数的Array.from()的TS函数?

如果父级被删除或删除,如何自动删除子级?

使用 Next.js 路由重定向到原始 URL 不起作用