简体   繁体   English

如何修复嵌套对象的“类型'K扩展X [T]?keyof K:从不'不能用于索引类型”?

[英]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”:

sandbox 沙盒

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.可以从中推断出idname键。

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
    }
  })
}

See Playground 见游乐场


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>,
) {

Playground 操场


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:我觉得这个解决方案可以接受,因为:

  1. It's entirely internal to the function and therefore does not affect the overall types of your program.它完全在函数内部,因此不会影响程序的整体类型。
  2. It has the correct runtime checks to ensure that 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 | numberid永远无法融入其中。

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.propobject.[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.

相关问题 Typescript 'keyof InstanceType<t> ' 不能用于索引类型错误</t> - Typescript 'keyof InstanceType<T>' cannot be used to index type error Typescript 泛型类型 - 类型“keyof T1”不能用于索引类型 - Typescript generic type - Type 'keyof T1' cannot be used to index type T[keyof T] 不能用于索引 {} - T[keyof T] cannot be used to index {} 类型“X”不能用作索引类型 - Type 'X' cannot be used as an index type 打字稿:嵌套对象的深度keyof,具有相关类型 - Typescript: deep keyof of a nested object, with related type 如何修复 Element 隐式具有“任何”类型,因为“字符串”类型的表达式不能用于索引类型? - how fix Element implicitly has an 'any' type because expression of type 'string' can't be used to index type? 如何更改索引表达式的类型? 即:c [k] - How to change type of index expression? ie: c[k] Typescript:`let result:{[key:T [K]]:T} = {};`不起作用,如何基于泛型键入对象? - Typescript: `let result: { [key: T[K]]: T } = {};` is not working, how can I type my object based on generics? 类型“ {}”不能用作索引类型 - Type '{}' cannot be used as an index type “never”类型上不存在属性“country”,并且类型“string”不能用于索引类型“{}” - Property 'country' does not exist on type 'never' and type 'string' can't be used to index type '{}'
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM