英文:
How to implement a TypeScript generic type for nested properties that resolves to their types?
问题
以下是翻译好的部分:
如何创建一个通用类型,该类型解析为嵌套属性的类型
假设我有一个配置类型
```typescript
type Config = {
parent: {
child: boolean
}
}
我想要类似于以下的东西:
type ChildConfigType = NestedType<Config, 'parent.child'> // boolean
type ParentConfigType = NestedType<Config, 'parent'> // {child: boolean}
我有一个用于非嵌套类型的基本实现
function getProp<Type extends object, Key extends keyof Type>(key: Key): Type[Key] {
return {} as Type[Key]
}
getProp<Config>('parent')
我还有一些非嵌套的辅助类型,可以正常工作
type PropsOf<Type extends object> = keyof Type
type TypeOfProp<Type extends object, Key extends PropsOf<Type>> = Type[Key]
function getPropWithHelpers<Type extends object, Key extends PropsOf<Type>>(key: Key): TypeOfProp<Type, Key> {
return {} as TypeOfProp<Type, Key>
}
我还有一个解析为嵌套属性的扁平联合的NestedPropsOf辅助类型
type NestedPropsOf<Type extends object> =
{
[key in keyof Type & (string | number)]: Type[key] extends object
? `${key}` | `${key}.${NestedPropsOf<Type[key]}>`
: `${key}`
}[keyof Type & (string | number)]
type NestedConfigProps = NestedPropsOf<Config> // 'parent' | 'parent.child'
我试图实现一个带有以下签名的函数
function getNestedProp<Type extends object, Key extends NestedPropsOf<Type>>(key: Key): NestedTypeOfProp<Type, Key> {
return {} as NestedTypeOfProp<Type, Key>
}
getNestedProp<Config>('parent.child')
我开始相信这可能是不可能的,因为类型无法直接操作字符串,所以我无法删除路径的第一个段。
<details>
<summary>英文:</summary>
How to create a generic type that resolves to the type of a nested property
Say I have a configuration type
type Config = {
parent: {
child: boolean
}
}
I would like something similar to
type ChildConfigType = NestedType<Config, 'parent.child'> // boolean
type ParentConfigType = NestedType<Config, 'parent'> // {child: boolean}
I have a basic implementation for non nested types
function getProp<
Type extends object,
Key extends keyof Type
>(key: Key): Type[Key] {
return {} as Type[Key]
}
getProp<Config>('parent')
I also have non nested helper types that work
type PropsOf<
Type extends object
> = keyof Type
type TypeOfProp<
Type extends object,
Key extends PropsOf<Type>
> = Type[Key]
function getPropWithHelpers<
Type extends object,
Key extends PropsOf<Type>
>(key: Key): TypeOfProp<Type, Key> {
return {} as TypeOfProp<Type, Key>
}
I also have a NestedPropsOf helper that resolves to a flattened union of the nested properties
type NestedPropsOf<Type extends object> =
{
[key in keyof Type & (string | number)]: Type[key] extends object
? ${key}
| ${key}.${NestedPropsOf<Type[key]>}
: ${key}
}[keyof Type & (string | number)]
type NestedConfigProps = NestedPropsOf<Config> // 'parent' | 'parent.child'
And I am trying to implement a function with the signature
function getNestedProp<
Type extends object,
Key extends NestedPropsOf<Type>
>(key: Key): NestedTypeOfProp<Type, Key> {
return {} as NestedTypeOfProp<Type, Key>
}
getNestedProp<Config>('parent.child')
I am starting to believe this is not possible because the types can't directly manipulate strings so I cannot remove the first segment of the path.
[Ts Playground][1]
[1]: https://www.typescriptlang.org/play?#code/C4TwDgpgBAwg9gOwGYEsDmUC8UDeAoKQqMAQwCcIFgAuXAohgYwAsUAbAE1oCM442IJBPUIBfPOLxIArgkbAUiKGgjAACmThgAPCKgAVcNAgAPYJQ4BnKHG4ArCPIA0egNIQQUU%20YRWoAaw84JAMjPAA%20AApAkFp3EABKWkNIAG14gF06BgpgaTIEXFEoEmsUiHSPDIk8WoB6OqgAVUsUBAxmCDZIMihQSEta-ugNLUsAeSRdBnKvMwtrWwd5CKwAoJDyoaNQyEnRnT1Z7wWbe0dgFwZ4uZ8-A4mp8vDV7HLKkGqpWXlFQpV1JowAB1FDAZgACS6PUs0yIx3mvkW52cbg8t1OD0m2meEWiHjiHiSuwg%20yBOKMTig8XC2SIuXyhRwxVKJLJWgpkCpNJq9UaLTaGAQEEs5g4UE63QgZEGeGGUAAciKxVinjsTkizstgLTMPR8AxUjEoG11iBgiSoAAyKCRUVkQVQAA%20UAQ0gAttxpQkMskjEaqhjNUsLnoAPxQAAGABIcDFRJHnVHY-GAHSxpWiiAcVWcioxDLhBN6WgxuMeYtiAPmzY7G124AO9pJt2e71feUAWRADzWmZVQMe2hwpAoVFoOBY7C4UF4-EECFEohecp2-ez5XZhxm6sRfhDqOu6I1fnXOcH2OeSftgtedMIDSgAHdmCRgLz5WpyJRgLNsGfNyQA5tHgZB0CpAByUcfwgl55RgVhOD-RVlQ3IwtxAxBUDQSDoKoVMp04WDahkOQFCUAEz2Ao5dzuZFtSuIgbhPawqIvNVIBeKIYkJRJaAA9CgPJcpuQ8WkDXpVRGSKEpWNQjhAOAkTqTEiQgA
</details>
# 答案1
**得分**: 2
以下是翻译好的部分:
可以使用 [template literals](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html#inference-with-template-literals) 和 [infer](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#type-inference-in-conditional-types) 关键字来操纵字符串。
实用工具:
`Dot` 用于连接两个字符串,使用 `.`:
```typescript
type Dot<T extends string, U extends string> = "" extends U ? T : `${T}.${U}`;
StopFields
是应该在类型中停止继续的类型;在我们的情况下,这些是基本类型:
type StopFields = string | number | boolean | symbol;
PathToFields
使用 mapped types 返回传递类型属性的所有可能路径。它会忽略数组,可以进行调整。
type PathsToFields<T> = T extends StopFields
? ""
: {
[K in Extract<keyof T, string>]: NonNullable<T[K]> extends Array<any>
? never
: K | Dot<K, PathsToFields<T[K]>>;
}[Extract<keyof T, string>];
实现:
type NestedType<T, Path extends PathsToFields<T>> = Path extends keyof T
? T[Path]
: Path extends `${infer Property extends keyof T & string}.${infer SubField}`
? SubField extends PathsToFields<T[Property]>
? NestedType<T[Property], SubField>
: never
: never;
NestedType
期望一个类型和其字段路径。它首先检查路径是否是对象的属性,如果是,返回该属性下的类型,否则使用 .
分割路径,左侧部分应该是传递对象的键,.
后面是剩余路径。如果所有条件都为真,我们会调用 NestedType
以获取从路径分割中获得的属性和传递的剩余路径。
用法:
type ChildConfigType = NestedType<Config, "parent.child">; // boolean
type ParentConfigType = NestedType<Config, "parent">; // {child: boolean}
英文:
It is actually possible to manipulate strings using template literals and infer keyword.
Utilities:
Dot
is used to concatenate two strings with .
:
type Dot<T extends string, U extends string> = "" extends U ? T : `${T}.${U}`;
StopFields
is the type on which we should stop going further in the types; in our case, those are primitives:
type StopFields = string | number | boolean | symbol;
PathToFields
returns all possible paths to properties of the passed type using mapped types. It ignores arrays, which can be adjusted.
type PathsToFields<T> = T extends StopFields
? ""
: {
[K in Extract<keyof T, string>]: NonNullable<T[K]> extends Array<any>
? never
: K | Dot<K, PathsToFields<T[K]>>;
}[Extract<keyof T, string>];
Implementation:
type NestedType<T, Path extends PathsToFields<T>> = Path extends keyof T
? T[Path]
: Path extends `${infer Property extends keyof T & string}.${infer SubField}`
? SubField extends PathsToFields<T[Property]>
? NestedType<T[Property], SubField>
: never
: never;
NestedType
expects a type and its paths to fields. It first checks if the path is the property of the object, in that case, returns the type under that property, otherwise splits the path with .
where the left part should be the key of the passed object and the part after the .
is the remaining path. If all conditions are true, we invoice NestedType
for the property that we got from splitting the path and passing the remaining path.
Usage:
type ChildConfigType = NestedType<Config, "parent.child">; // boolean
type ParentConfigType = NestedType<Config, "parent">; // {child: boolean}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论