为了让它起作用,你需要将你的函数设为generic.这是允许多个输入类型相互依赖的唯一方法.这里有一种写它的方法:
const includesByAttribute = <
K extends PropertyKey,
T extends Record<K, string | number | boolean>
>(
attr: K,
list: T[],
val: NoInfer<T>,
) => {
if (list.length > 0) return list.find((obj) => obj[attr] === val[attr]);
return false;
};
在这里,类型参数K
对应于输入attr
,是constrained到PropertyKey
,这意味着它应该是keylike(它是string | number | symbol
;如果需要,您可以将其设置为string
).
并且对应于list
输入的元素的类型参数T
被约束为Record<K, string | number | boolean>
(使用the Record
utility type),这意味着它在键K
处至少应该具有对应于attr
的string | number | boolean
属性.这并不要求T
的every属性是Primitive
,只需要位于键K
的那个.
您也希望val
是T
类型,但我已经编写了NoInfer<T>
,以表明您不希望编译器从val
变为infer T
.如果您将其保留为T
,那么编译器将从list
和val
中推断出T
,并且它可能允许在val
中存在不存在于list
元素中的额外键.也就是说,您希望编译器从list
only推断出T
,然后只根据它推断出checkval
.这样,如果您将额外的键添加到val
,编译器就会发出错误.
所以NoInfer<T>
的计算结果是T
,但"阻止"或"屏蔽"推论.在TypeScrip5.3中,没有内置的NoInfer<T>
实现,而在microsoft/TypeScript#14829中有一个长期未解决的问题需要它.在对该问题的讨论中存在它的各种用户实现,这些实现适用于各种用例.在in a comment中提到了一个简单的例子:仅intersect个对象类型为空的{}
,以降低推理优先级:
type NoInfer<T> = T & {}
这适用于您的示例.但下一个版本的TypeScrip很可能包括microsoft/TypeScript#56794,它本身就实现了NoInfer<T>
.在这一点上,您可以放弃关于它的定义,它仍然会表现出来.
好吧,让我们测试一下:
includesByAttribute('x', [{ x: 1 }], { x: 2 }); // okay
includesByAttribute('value', [{ value: 1, x: 1 }], { value: 2, x: 2 }); // okay
includesByAttribute('value', [{ x: 1 }], { x: 2 }); // error, x is not known
includesByAttribute('value', [{ value: 1, x: 1, z: 8 }], { value: 2, x: 2 }); // error, z missing
看上go 不错.编译器根据需要接受前两个调用并拒绝后两个调用.
Playground link to code个