我有一个通用函数,如下所示:
function joinBy<T, U, K>(data1: Array<T>, data2: Array<U>, key: K) {
return data2.map( datum2 => {
return data1.find( datum1 => datum1[key] === datum2[key] )
})
}我想要做的是将K约束为T和U共有的字符串。例如,给定以下类型:
type Customer = {
customerId: number,
name: string,
email?: string
}
type Order = {
customerId: number,
orderId: number,
items: Array<Item>
}K的唯一有效值应该是customerId,因为这两种类型都很常见。如果这两种类型都有另一个共同的字段,例如foo,那么K应该是联合'customerId' | 'foo'。
如果我将函数签名修改为以下内容:
function joinBy<T, U, K extends keyof T & keyof U>(data1: Array<T>, data2: Array<U>, key: K) {
return data2.map( datum2 => {
return data1.find( datum1 => datum1[key] === datum2[key] )
})
}datum1[key] === datum2[key]上的打字错误,说是types T[K] and U[K] have no overlap。从这个吉特布问题中我知道,有时这个错误可能是一个假阳性,但我不确定这是否是其中之一,或者是否有更好的方法为K构建类型约束。
有没有更好的方法来做这件事,还是我只是看到一个假阳性?谢谢!
发布于 2020-08-21 16:47:05
为了使您的操作是类型安全的,您需要确保不仅K在keyof T和keyof U中,而且T[K]和U[K]是相同类型的(实际上,它们通过===是可比较的)。否则,你可能会发现自己接受了这类事情:
interface Tree {
name: string;
age: number;
bark: string;
}
interface Dog {
name: string;
age: number;
bark(): void;
}
declare const trees: Tree[];
declare const dogs: Dog[];
joinBy(trees, dogs, "bark"); Tree和Dog都有一个名为bark的属性,但它们是不可比较的。
有一些方法可以收紧签名以要求T[K]和U[K]兼容.事实上,有多种方法。
TS的一个重要警告是,在某些情况下,特别是在未指定泛型类型参数(如T、U和K在joinBy()实现中)的情况下,人类可以看到某些东西是类型安全的,但编译器不能,因为编译器没有对泛型类型执行正确的“高阶”推理。
因此,为这样的函数设计类型签名的部分技巧不仅是得到正确的约束,而且编译器可以验证函数实现内部的类型安全性。这并不总是可能的,因此有时您必须在实现中使用类型断言或类似的方法。
但是,对于您的函数,有一个“对编译器友好的”类型:
function joinBy<T, K extends keyof T>(data1: T[], data2: (Pick<T, K>)[], key: K) {
return data2.map(datum2 => {
return data1.find(datum1 => datum1[key] === datum2[key])
})
}在这里,编译器只需要关心data1数组的元素类型data1,并且key的类型K是它已知的键之一。然后,对data2的约束是,它必须是可分配给Pick<T, K>的某种类型的数组:也就是说,具有与T的属性兼容的K属性的类型。我们并不真正关心特定类型data2的元素是什么,因为我们只关注它的key属性;我们不返回该元素类型的任何值,因此我们不需要花费任何努力来推断它的类型参数U。
编译器很乐意允许datum1[key] === datum2[key],因为它把它们都看作是T[K]类型(或者Pick<T, K>[K],幸运地被认为是兼容的)。
让我们确保它有效:
declare const customers: Customer[];
declare const orders: Order[];
joinBy(customers, orders, "customerId"); // ok
joinBy(customers, orders, "name"); // error!
// -------------> ~~~~~~
// 'name' is missing in Order
joinBy(trees, dogs, "age"); // ok
joinBy(trees, dogs, "bark"); // error!
// ---------> ~~~~
// 'bark' property incompatible那就太好了。
但是,正如您在评论中提到的,感觉调用签名上的错误出现在了错误的位置。您可能希望看到"name"和/或"bark"是错误的来源,而不是orders或dogs。这是可以实现的,但(可能)并不是编译器在实现中能够理解的方式。为了实现这一点,我将使用类似于类型断言的东西:单个调用签名过载:让调用签名对调用者来说是一个好签名,而实现签名对于实现来说是一个很好的签名:
type CompatibleKeys<T, U> = {
[K in keyof T & keyof U]: U[K] extends T[K] ? K : T[K] extends U[K] ? K : never
}[keyof T & keyof U];
function joinBy<T, U, K extends CompatibleKeys<T, U>>(
data1: T[],
data2: U[],
key: K
): (T | undefined)[];
function joinBy(data1: any[], data2: any[], key: PropertyKey) {
return data2.map(datum2 => {
return data1.find(datum1 => datum1[key] === datum2[key])
})
}实现签名有意地充满了any;如果您愿意,可以将原来的签名保留在T和K中,但在某种意义上,只要您愿意将调用端与实现端分开,就不重要了:在实现中,无论您想要什么,都要确保自己确信它的类型安全,然后给调用方一个符合您需要的签名。
让我们看看它现在的表现:
joinBy(customers, orders, "name"); // error!
// ---------------------> ~~~~~~
// Argument of type '"name"' is not assignable to parameter of type '"customerId"'.
joinBy(trees, dogs, "bark"); // error!
// ---------------> ~~~~~~
// Argument of type '"bark"' is not assignable to parameter of type 'CompatibleKeys<Tree, Dog>'现在,错误出现在key参数上,它们或多或少地告诉您问题是什么。耶。
但是,不确定在T和U之间实际上没有重叠的情况下,您想要做什么。以下应该是一个错误,但什么错误?
joinBy(orders, trees, "name"); // error!
// -----------------> ~~~~~~
// Argument of type 'string' is not assignable to parameter of type 'never'.我真的觉得trees是这里的错误,因为没有有效的key可以编写。为了实现这一点,您可以开始为joinBy()签名添加更多的复杂性.也许是这样:
type SomeCompatibleType<T> = { [K in keyof T]: Pick<T, K> }[keyof T]
function joinBy<T, U extends SomeCompatibleType<T>, K extends CompatibleKeys<T, U>>(
data1: T[],
data2: U[],
key: K
): (T | undefined)[];
// impl elided现在您在key上得到了错误,除非没有重叠,在这种情况下,data2上有错误
joinBy(orders, trees, "name"); // error!
// ----------> ~~~~~
// Argument of type 'Tree[]' is not assignable to parameter of type 'SomeCompatibleType<Order>[]'.又来了。也许不是..。到现在为止,代码太复杂了,我不愿意把它放在重要的地方。可能会有奇怪的边缘情况,很多人都看不清它在做什么。所以备份,编译器友好的签名可能是我真正建议的。
https://stackoverflow.com/questions/63526387
复制相似问题