[英]How to fix "Type 'K extends X[T] ? keyof K : never' cannot be used to index type" for nested object?
I'm trying to create a function where the first parameter (data) is an array of type Product, the second (element) is any property inside Product and the third and fourth parameters are properties of "element".我正在尝试创建一个函数,其中第一个参数(数据)是 Product 类型的数组,第二个(元素)是 Product 内部的任何属性,第三个和第四个参数是“元素”的属性。 So let's say "Product" has the property "Customer", I want to get "Id" and "Name" out of Customer.
因此,假设“产品”具有“客户”属性,我想从客户中获取“ID”和“名称”。
I have the following code我有以下代码
function getFilterItem<T extends keyof Product, K extends Product[T]>(
data: Product[],
element: T,
id: K extends Product[T] ? keyof K : never,
name: K extends Product[T] ? keyof K : never,
): FilterItem[] {
// removed for simplicity
}
What I want is to be able to call it like so我想要的是能够这样称呼它
getFilterItem(data, 'customer', 'id', 'name');
With the code above, it identifies correctly the parameters, so if I pass a property that doesn't exist in 'customer' it'll tell me it's wrong.使用上面的代码,它可以正确识别参数,所以如果我传递一个在“客户”中不存在的属性,它会告诉我这是错误的。 My problem is when I try to get the property it'll say "Type 'K extends Product[T] ? keyof K : never' cannot be used to index type".
我的问题是,当我尝试获取属性时,它会显示“Type 'K extends Product[T] ? keyof K : never' cannot be used to index type”。
Now, correct me if I'm wrong, but I imagine it's complaining because Product[T] could be any type in Product, let's say "Product" has the property "price" which is a number, so using something like getFilterItem(data, 'price', 'id', 'name');
现在,如果我错了,请纠正我,但我想这是在抱怨,因为 Product[T] 可以是 Product 中的任何类型,假设“Product”具有属性“price”,它是一个数字,所以使用类似
getFilterItem(data, 'price', 'id', 'name');
typescript wouldn't be able to index it using the third and fourth parameter.打字稿无法使用第三个和第四个参数对其进行索引。
This is the full code (I've removed some logic to make it simpler)这是完整的代码(我删除了一些逻辑以使其更简单)
function getFilterItem<T extends keyof Product, K extends Product[T]>(
data: Product[],
element: T,
id: K extends Product[T] ? keyof K : never,
name: K extends Product[T] ? keyof K : never,
): FilterItem[] {
data.forEach((product) => {
const value = product[element];
if (value) {
const valueId = value[id]; // here the eror is shown
}
}
}
How can I make it accept 'id' as a key of 'Product[T]'?我怎样才能让它接受 'id' 作为 'Product[T]' 的键?
Here is a working example where I want to get "id" and "autonomyLevel" from a property inside Product:这是一个工作示例,我想从 Product 内部的属性中获取“id”和“autonomyLevel”:
If I follow you right, you have something like:如果我正确地跟随你,你有类似的东西:
interface Product {
customer: Customer
}
interface Customer {
id: number
name: string
}
And you want the third and fourth arguments to the keys of object you would get by accessing the first argument by the second argument.并且您希望通过第二个参数访问第一个参数来获得对象键的第三个和第四个参数。
declare const data: Product
getFilterItem([data], 'customer', 'id', 'name');
Then you only need one generic type parameter, the "customer"
key.那么您只需要一个泛型类型参数,即
"customer"
键。 The id
and name
keys can be inferred from that.可以从中推断出
id
和name
键。
function getFilterItem<T extends keyof Product>(
data: Product[],
element: T,
id: keyof Product[T],
name: keyof Product[T],
) {
data.forEach((product) => {
const value = product[element];
if (value) {
const valueId = value[id]; // works
}
})
}
The properties may be null
属性可能为空
In that case you my solution gives you never
for the secondary keys because the type of Product[T]
is something like AutonomyLevel | never
在这种情况下,我的解决方案
never
为您提供辅助键,因为Product[T]
的类型类似于AutonomyLevel | never
AutonomyLevel | never
. AutonomyLevel | never
。 And the keyof
that is never
because both types in that union share no keys in common.并且
keyof
never
因为该联合中的两种类型没有共同的密钥。
But if you Exclude
undefined from the type you are getting the keyof
then it works:但是,如果您从获取
keyof
的类型中Exclude
未定义,那么它可以工作:
function getFilterItem<T extends keyof Product>(
data: Product[],
element: T,
id: keyof Exclude<Product[T], undefined>,
name: keyof Exclude<Product[T], undefined>,
) {
However, this presents a new problem:但是,这带来了一个新问题:
function getFilterItem<T extends keyof Product>(
data: Product[],
element: T,
id: keyof Exclude<Product[T], undefined>,
name: keyof Exclude<Product[T], undefined>,
) {
data.forEach((product) => {
const value = product[element];
if (value) {
const valueId = value[id];
// Type 'keyof Exclude<Product[T], undefined>' cannot be used to index type 'string | number | ProductType | AutonomyLevel | ProductStatus | Industry[] | LifeCycle[]'.(2536)
}
})
}
The typescript compiler can have a hard time matching up differently derived generic types.打字稿编译器可能很难匹配不同派生的泛型类型。 In this case we've done the work to make this type safe, but Typescript doesn't believe us.
在这种情况下,我们已经完成了使这种类型安全的工作,但 Typescript 不相信我们。
Honestly, and I very very rarely recommend this, I might add a little runtime check and then mark it as an expected error:老实说,我很少推荐这个,我可能会添加一点运行时检查,然后将其标记为预期错误:
data.forEach((product) => {
const value = product[element];
if (value && id in value) {
// @ts-expect-error The compiler has a hard time understanding
// that `id` is guaranteed to be a valid key of `value`.
const valueId: Product[T] = value[id];
}
})
I find this solution acceptable because:我觉得这个解决方案可以接受,因为:
id
is indeed a key in the value
object.id
确实是value
对象中的键。 It's unfortunate to unfortunate to disable the type checker in this case, and I'm open to other ways to eliminate it, but in this case, I this it's reasonably safe-ish.在这种情况下禁用类型检查器是很不幸的,我愿意接受其他方法来消除它,但在这种情况下,我认为这是相当安全的。
Notice also that if what you want is the array element id
value as the code suggests, you would not need to provide the prop name as an argument to the function as in here .另请注意,如果您想要的是代码建议的数组元素
id
值,则不需要像这里那样提供道具名称作为函数的参数。 For that, T
would be the common props for all the types shared in Product
keys, and not the keys themselves in the case more that one was shared, for the provided code only seems to be the id
.为此,
T
将是Product
密钥中共享的所有类型的通用道具,而不是在共享更多的情况下的密钥本身,因为提供的代码似乎只是id
。
One point I'd like to make is that in case its not clear if typescript's behaviour makes sense I'd like to provide this explanation.我想说的一点是,如果不清楚打字稿的行为是否有意义,我想提供这个解释。 The
id: keyof Exclude<Product[T], undefined>
enables you to call the getFilterItem
with the 'id'
argument. id: keyof Exclude<Product[T], undefined>
使您能够使用'id'
参数调用getFilterItem
。 You'd need this as long there are optional arguments in the Product
type.只要
Product
类型中有可选参数,您就需要这个。
Taken that into account, notice the core of the problem is that what is being trying to be done is get the index of a type Product[T]
with the type keyof Exclude<Product[T], undefined>
.考虑到这一点,请注意问题的核心是,正在尝试做的是获取具有类型
keyof Exclude<Product[T], undefined>
的 Product[ Product[T]
类型的索引。 Which are different types (meaning the index, T
is a different type from keyof Exclude<Product[T], undefined>)
, as one may hold undefineds due to optional arguments and the other may not.哪些是不同的类型(意味着索引,
T
是与keyof Exclude<Product[T], undefined>)
,因为一个可能由于可选参数而持有未定义,而另一个可能没有。 See playground .见操场。 Removed the id and the image becase due to the keyof and Lookup Types you are are also narrowing down your type fit to
string | number
删除了 id 和图像,因为由于keyof 和查找类型,您也在缩小适合
string | number
string | number
, into whom id
would never fit in. string | number
, id
永远无法融入其中。
For me the error makes sense because regarding type safety should not be allowed to exclude undefined
for the id
argument in getFilterItem
, and then allowing it back for the index check of Product[T]
with T holding undefineds as there are optional arguments.对我来说,这个错误是有道理的,因为关于类型安全,不应允许为
getFilterItem
中的id
参数排除undefined
,然后允许它返回用于Product[T]
的索引检查,其中 T 持有 undefineds,因为有可选参数。
Another thing I'd like to mention is that I think due to keyof and Lookup Types there might be different behaviours accessing props with object.prop
and object.[prop]
我想提到的另一件事是,我认为由于keyof 和 Lookup Types可能会有不同的行为访问带有
object.prop
和object.[prop]
Check what I mean in this playground .检查我在这个操场上的意思。 Where
value.id
works/compiles but value[id]
does not. value.id
工作/编译但value[id]
不工作的地方。
So there are 2 things that would need to be fixed here regarding the type definition因此,关于类型定义,这里需要修复两件事
1.- If undefined/optional args are allowed 1.- 如果允许未定义/可选参数
2.- Handle the property access 2.- 处理属性访问
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.