打字稿:基于参数中存在的键的映射返回类型

时间:2019-02-21 14:43:54

标签: typescript

假设我正在编写一个数据库接口,通过该接口我可以一次提交多个查询,作为一个以表名作为键的对象,并希望接收一个结果与查询中的键相同的对象。

interface Query {
    users?: UsersQuery
    products?: ProductsQuery
    orders?: OrdersQuery
}
interface Result {
    users?: User[]
    products?: Product[]
    orders?: Order[]
}

declare function readData(query: Query): Result;

有什么方法可以根据传入对象中实际存在哪些键来更详细地说明返回类型?

似乎我与以下内容非常接近:

declare function readData<T extends Query>(query: T): { [K in keyof T]: T[K] }

但是,我需要使用T[K],而不是使用Result[K]进行编译,而不是使用error TS2536: Type 'K' cannot be used to index type 'Result'映射的返回类型。根据文档,我猜想是因为T[K]之外的任何东西都不是同态的,尽管我对此并不十分清楚。

在使用映射类型时,这里是否缺少我的东西?还是有其他方法可以做到这一点?

3 个答案:

答案 0 :(得分:1)

首先将UserQueryProductQuery等推广为使用通用的通用基本类型。例如:

// Entity definitions
type User = { userId: string };
type Product = { productId: string };
type Order = { orderId: string };

type EntityQuery<T> = { [K in keyof T]: T[K] }
type ResultOf<T> = T extends EntityQuery<infer I> ? I[] : undefined;

然后像这样定义您的Query接口:

interface Query {
    users?: EntityQuery<User>
    products?: EntityQuery<Product>
    orders?: EntityQuery<Order>
}

现在,您可以像这样将readData设为通用名称:

declare function readData<T extends Query>(query: T): { [K in keyof T]: ResultOf<T[K]> };

答案 1 :(得分:0)

在这种情况下,类型如何工作的更为意外的后果之一是,由于T extends Query不仅可以具有Query的属性,而且只要存在一些重叠,它就可以具有检查查询对象是否正确的任何键:

declare function readData<T extends Query>(query: T): { [K in keyof T]: T[K] }

readData({
    bla: 1, // not in Query but the compiler is fine, no excess property checks since T just has to extend Query
    orders: null
})

这也是导致您收到T的错误的原因,该错误可能还有查询中的其他键。

一种选择是将函数的参数指定为Pick中的Query,所选择的键是函数的类型参数:

declare function readData<K extends keyof Query>(query: Pick<Query, K>): { [P in K]: Result[K] }


readData({
    users: null!,
    bla: 0 // error now
})

虽然确实可以按预期进行类型检查,但问题是它不会在对象文字的键上提供代码完成,这是不幸的。

如果我们添加与Query部分的交集,我们将获得良好的代码完成率,并在K中捕获传入的实际键(尽管它们可以是未定义的,但您可能已经检查)

declare function readData<K extends keyof Query>(query: Pick<Query, K> & Partial<Query>): { [P in K]: Result[K] }


readData({
    users: null!,
    // we get suggestions here
})

答案 2 :(得分:0)

您在正确的轨道上!

我会做这样的事情:

// Create a mapping between the query type used, and the result type expected:
type QueryResult<T> =
    T extends UsersQuery ? User[] :
    T extends ProductsQuery ? Product[] :
    T extends OrdersQuery ? Order[] :
    never
;

// Same as what you started with, but using our new QueryResult<T> mapping:
declare function readData<T extends Query>(query: T): { [K in keyof T]: QueryResult<T[K]> };

这应该根据您传入的内容为您提供正确的输入方式。

作为参考,我使用以下接口对此进行了测试:

interface UsersQuery {
    user_id: string;
}

interface ProductsQuery {
    product_id: string;
}

interface OrdersQuery {
    order_id: string;
}

interface User {
    _id: string;
    userName: string;
}

interface Product {
    _id: string;
    productName: string;
}

interface Order {
    _id: string;
    orderName: string;
}

interface Query {
    users?: UsersQuery;
    products?: ProductsQuery;
    orders?: OrdersQuery;
}