基于对象键的递归类型

huangapple go评论78阅读模式
英文:

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: &#39;hello aaa&#39;,
    }
  },
  b: {
    bb: &#39;hello bb&#39;,
  }
} as const;

// should work
foo&lt;typeof values&gt;(&#39;a&#39;)(&#39;aa&#39;)(&#39;aaa&#39;);
foo&lt;typeof values&gt;(&#39;b&#39;)(&#39;bb&#39;);

// should fail
foo&lt;typeof values&gt;(&#39;c&#39;);
foo&lt;typeof values&gt;(&#39;a&#39;)(&#39;b&#39;);

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: &#39;hello aaa&#39;,
    }
  }
} as const;

const valuesB = {
  a: {
    aa: {
      aaa: &#39;hello aaa&#39;,
    }
  },
  b: {
    bb: {
      bbb: &#39;hello bbb&#39;,
    }
  }
} as const;


function foo&lt;T&gt;() {
  return function (key: keyof T) {
    type U = T[typeof key];
    return foo&lt;U&gt;();
  };
}

const booA = foo&lt;typeof valuesA&gt;();
const booB = foo&lt;typeof valuesB&gt;();

booA(&#39;a&#39;)(&#39;aa&#39;)(&#39;aaa&#39;); // WORKS! :D
booB(&#39;a&#39;)(&#39;aa&#39;);        // FAILS! :(

booB give the following error message:

Argument of type &#39;string&#39; is not assignable to parameter of type &#39;never&#39;.

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');

查看 Playground

英文:

Checkout this recursive conditional type:

type ValueOrFn&lt;T&gt; = &lt;K extends keyof T&gt;(key: K) =&gt;
  T[K] extends object
    ? ValueOrFn&lt;T[K]&gt;
    : 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&lt;T[K]&gt; if we have an object and therefore expect further drill ins.

Let's try it:

declare const foo: ValueOrFn&lt;typeof values&gt;

const test1A = foo(&#39;a&#39;)(&#39;aa&#39;)(&#39;aaa&#39;) // &#39;hello aaa&#39;
const test1B = foo(&#39;b&#39;)(&#39;bb&#39;) // &#39;hello bb&#39;

One thing to note here is that this can't work:

foo&lt;typeof values&gt;(&#39;a&#39;);

This is because the invocation of foo requires two generic type parameters:

  • typeof values The object to drill into.
  • &#39;a&#39; 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 &#39;a&#39;). 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: &lt;T&gt;() =&gt; ValueOrFn&lt;T&gt;

const test2A = fooWrapped&lt;typeof values&gt;()(&#39;a&#39;)(&#39;aa&#39;)(&#39;aaa&#39;);
const test2B = fooWrapped&lt;typeof values&gt;()(&#39;b&#39;)(&#39;bb&#39;);
//                                      ^ 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: &lt;T&gt;(obj: T) =&gt; ValueOrFn&lt;T&gt;

const test3A = fooObj(values)(&#39;a&#39;)(&#39;aa&#39;)(&#39;aaa&#39;);
const test3B = fooObj(values)(&#39;b&#39;)(&#39;bb&#39;);

See Playground

huangapple
  • 本文由 发表于 2023年1月5日 05:38:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/75011615.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定