英文:
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 values
The 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');
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论