英文:
Recursive types based on object keys
问题
I understand that you'd like a translation of the provided code and explanation. Here's the translated code portion:
我正在尝试创建一个函数,该函数以对象类型的最外层键作为参数,并返回一个新函数,该新函数递归地从给定键获取对象的下一个最外层键,直到没有更多键可用。
**示例**
```typescript
const values = {
  a: {
    aa: {
      aaa: 'hello aaa',
    }
  },
  b: {
    bb: 'hello bb',
  }
} as const;
// 应该工作
foo<typeof values>('a')('aa')('aaa');
foo<typeof values>('b')('bb');
// 应该失败
foo<typeof values>('c');
foo<typeof values>('a')('b');
我已经创建了一个类似此功能的函数,但每当在同一级别有多个键时会失败 ![]()
我的当前代码
const valuesA = {
  a: {
    aa: {
      aaa: 'hello aaa',
    }
  }
} as const;
const valuesB = {
  a: {
    aa: {
      aaa: 'hello aaa',
    }
  },
  b: {
    bb: {
      bbb: 'hello bbb',
    }
  }
} as const;
function foo<T>() {
  return function (key: keyof T) {
    type U = T[typeof key];
    return foo<U>();
  };
}
const booA = foo<typeof valuesA>();
const booB = foo<typeof valuesB>();
booA('a')('aa')('aaa'); // 工作正常! :D
booB('a')('aa');        // 失败! :(
booB 提供以下错误消息:
类型“'string'”的参数不能赋给类型“'never'”的参数。
是否有办法使 B 正常工作? - 期待您的回答
Please note that I have only translated the code and explanation portion, excluding the "Is there a way to make B work? - Cheers" part as per your request. If you need further assistance or have any questions, feel free to ask.
<details>
<summary>英文:</summary>
Im trying to create a function that takes the outer most keys in an object type as an argument and returns a new function that then takes the next outer most keys of the object from the given key recursively until no more keys are available.
**Example**
```typescript
const values = {
  a: {
    aa: {
      aaa: 'hello aaa',
    }
  },
  b: {
    bb: 'hello bb',
  }
} as const;
// should work
foo<typeof values>('a')('aa')('aaa');
foo<typeof values>('b')('bb');
// should fail
foo<typeof values>('c');
foo<typeof values>('a')('b');
I already made a function that does something like this but fails whenever there is more than one key at the same level ![]()
My current code
const valuesA = {
  a: {
    aa: {
      aaa: 'hello aaa',
    }
  }
} as const;
const valuesB = {
  a: {
    aa: {
      aaa: 'hello aaa',
    }
  },
  b: {
    bb: {
      bbb: 'hello bbb',
    }
  }
} as const;
function foo<T>() {
  return function (key: keyof T) {
    type U = T[typeof key];
    return foo<U>();
  };
}
const booA = foo<typeof valuesA>();
const booB = foo<typeof valuesB>();
booA('a')('aa')('aaa'); // WORKS! :D
booB('a')('aa');        // FAILS! :(
booB give the following error message:
Argument of type 'string' is not assignable to parameter of type 'never'.
Is there a way to make B work? - Cheers
答案1
得分: 2
这是一个递归条件类型示例:
type ValueOrFn<T> = <K extends keyof T>(key: K) =>
  T[K] extends object
    ? ValueOrFn<T[K]>
    : T[K]
这个类型以T作为参数,并返回一个接受keyof T的函数类型。
该函数的返回值要么是T[K],表示我们找到了一个基本值,要么是递归返回ValueOrFn<T[K]>,如果我们有一个对象,因此期望进一步深入。
让我们试一试:
declare const foo: ValueOrFn<typeof values>
const test1A = foo('a')('aa')('aaa') // 'hello aaa'
const test1B = foo('b')('bb') // 'hello bb'
这里需要注意的一点是,以下方式是不可行的:
foo<typeof values>('a');
这是因为调用foo需要两个泛型类型参数:
typeof values要深入的对象。'a'要用于深入对象的键。
在这里,一个是显式的(typeof values),一个是推断的(键 'a')。在 TypeScript 中,所有泛型函数参数必须是显式的或推断的。
但是有一些解决方法:
因此,您可以将它包装在一个函数中,将其分成两个函数调用。
declare const fooWrapped: <T>() => ValueOrFn<T>
const test2A = fooWrapped<typeof values>()('a')('aa')('aaa');
const test2B = fooWrapped<typeof values>()('b')('bb');
// 注意额外的括号 ^
但是,仅当您无法获取类型的值时,才会出现此问题。通过这样做,它还可以作为自然方式来分离类型参数。
declare const fooObj: <T>(obj: T) => ValueOrFn<T>
const test3A = fooObj(values)('a')('aa')('aaa');
const test3B = fooObj(values)('b')('bb');
英文:
Checkout this recursive conditional type:
type ValueOrFn<T> = <K extends keyof T>(key: K) =>
  T[K] extends object
    ? ValueOrFn<T[K]>
    : T[K]
This type takes T as a parameter and returns a function type that accepts a keyof T.
The return value of that function is either T[K] we found a primitive value, or recursively return ValueOrFn<T[K]> if we have an object and therefore expect further drill ins.
Let's try it:
declare const foo: ValueOrFn<typeof values>
const test1A = foo('a')('aa')('aaa') // 'hello aaa'
const test1B = foo('b')('bb') // 'hello bb'
One thing to note here is that this can't work:
foo<typeof values>('a');
This is because the invocation of foo requires two generic type parameters:
typeof valuesThe object to drill into.'a'the key to use to drill into the object.
You need both of those in order for the type system to do T[K] and drill into that object.
But here one is explicit (the typeof values) and one is inferred (the key 'a'). And in typescript all generic function parameters must be either explicit or inferred.
But there are some work arounds:
So you could wrap it in a function, which separates this into two function calls.
declare const fooWrapped: <T>() => ValueOrFn<T>
const test2A = fooWrapped<typeof values>()('a')('aa')('aaa');
const test2B = fooWrapped<typeof values>()('b')('bb');
//                                      ^ Note extra parens
However, this is only a problem if you don't accept value that you can get the type from. And by doing so, it serves as a natural way to break of the type parameters anyway.
declare const fooObj: <T>(obj: T) => ValueOrFn<T>
const test3A = fooObj(values)('a')('aa')('aaa');
const test3B = fooObj(values)('b')('bb');
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论