在 Typescript 中使用自定义键展平对象
Flatten object with custom keys in Typescript
我想扁平化对象并将返回值转换为类型。
例如:
const myObject = {
names: {
title: 'red',
subtitle: 'green'
},
}
const returned = [...Object.values(flatten(myObject))] as const
// returns ['red', 'green']
type Type = typeof returned[number]
返回的变量现在是 ['red'、'green']
类型应该是'红色| 'green',但是现在是一个字符串数组,因为返回的typeof是string[]。
我想使用这种类型为我的组件键入道具:
<Component name="red" /> //is correct, but
<Component name=`something different than "red" or "green"` /> //is incorrect.
展平函数:
type FlattenedObject = { [x: string]: string }
export const flattenDaisyChain = (obj: any): FlattenedObject => {
const result: FlattenedObject = {}
const transform = (wrapper: FlattenedObject | string, p?: string) => {
switch (typeof wrapper) {
case 'object':
p = p ? p + '.' : ''
for (const item in wrapper) {
transform(wrapper[item], p + item)
}
break
default:
if (p) {
result[p] = wrapper
}
break
}
}
transform(obj)
return result
}
首先,如果你想让编译器有机会意识到 Type
是 "red" | "green"
而不是 string
,我们必须确保 myObjct
的类型包括这些 literal types. The easy way to do that is with a const
assertion:
const myObject = {
names: {
title: 'red',
subtitle: 'green'
},
} as const;
接下来,您的 flatten()
函数的类型在 TypeScript 4.1+ 中几乎无法表示。我可能会这样写,使用 :
的答案
type Flatten<T extends object> = object extends T ? object : {
[K in keyof T]-?: (x: NonNullable<T[K]> extends infer V ? V extends object ?
V extends readonly any[] ? Pick<T, K> : Flatten<V> extends infer FV ? ({
[P in keyof FV as `${Extract<K, string | number>}.${Extract<P, string | number>}`]:
FV[P] }) : never : Pick<T, K> : never
) => void } extends Record<keyof T, (y: infer O) => void> ?
O extends infer U ? { [K in keyof O]: O[K] } : never : never
const flatten = <T extends object>(obj: T): Flatten<T> => { /* impl */ }
它使用 recursive conditional types, template literal types, and key remapping in mapped types 递归遍历所有属性并将键与中间的 .
连接在一起。我不知道这个特定类型的函数是否适用于 flatten()
的所有用例,我不敢假装它没有潜伏在其中的恶魔。
如果我使用该签名,并使用上面的 const
断言 myObject
调用 flatten(myObject)
,您会得到:
const flattenedMyObject = flatten(myObject);
/* const flattenedMyObject: {
readonly "names.title": "red";
readonly "names.subtitle": "green";
} */
万岁!这足以让 Type
成为 "red" | "green"
:
const returned = [...Object.values(flatten(myObject))] as const
type Type = typeof returned[number] // "red" | "green"
也万岁。
我想如果你真的不关心跟踪键名,你可以通过返回带有 string
index signature:
的东西来大大简化 flatten()
类型签名
type DeepValueOf<T> = object extends T ? object :
T extends object ? DeepValueOf<
T[Extract<keyof T, T extends readonly any[] ? number : unknown>]
> : T
const flatten = <T extends object>(obj: T): { [k: string]: DeepValueOf<T> } => {
/* impl */ }
产生
const flattenedMyObject = flatten(myObject);
/* const flattenedMyObject: {
[k: string]: "green" | "red";
} */
最终
const returned = [...Object.values(flatten(myObject))] as const
type Type = typeof returned[number] // "red" | "green"
我对 DeepValueOf
的担心不如对 Flatten
的担心,但如果没有一些边缘情况,我会感到惊讶。因此,在任何类型的生产代码中使用它之前,您都需要进行真正的测试。
我想扁平化对象并将返回值转换为类型。
例如:
const myObject = {
names: {
title: 'red',
subtitle: 'green'
},
}
const returned = [...Object.values(flatten(myObject))] as const
// returns ['red', 'green']
type Type = typeof returned[number]
返回的变量现在是 ['red'、'green']
类型应该是'红色| 'green',但是现在是一个字符串数组,因为返回的typeof是string[]。 我想使用这种类型为我的组件键入道具:
<Component name="red" /> //is correct, but
<Component name=`something different than "red" or "green"` /> //is incorrect.
展平函数:
type FlattenedObject = { [x: string]: string }
export const flattenDaisyChain = (obj: any): FlattenedObject => {
const result: FlattenedObject = {}
const transform = (wrapper: FlattenedObject | string, p?: string) => {
switch (typeof wrapper) {
case 'object':
p = p ? p + '.' : ''
for (const item in wrapper) {
transform(wrapper[item], p + item)
}
break
default:
if (p) {
result[p] = wrapper
}
break
}
}
transform(obj)
return result
}
首先,如果你想让编译器有机会意识到 Type
是 "red" | "green"
而不是 string
,我们必须确保 myObjct
的类型包括这些 literal types. The easy way to do that is with a const
assertion:
const myObject = {
names: {
title: 'red',
subtitle: 'green'
},
} as const;
接下来,您的 flatten()
函数的类型在 TypeScript 4.1+ 中几乎无法表示。我可能会这样写,使用
type Flatten<T extends object> = object extends T ? object : {
[K in keyof T]-?: (x: NonNullable<T[K]> extends infer V ? V extends object ?
V extends readonly any[] ? Pick<T, K> : Flatten<V> extends infer FV ? ({
[P in keyof FV as `${Extract<K, string | number>}.${Extract<P, string | number>}`]:
FV[P] }) : never : Pick<T, K> : never
) => void } extends Record<keyof T, (y: infer O) => void> ?
O extends infer U ? { [K in keyof O]: O[K] } : never : never
const flatten = <T extends object>(obj: T): Flatten<T> => { /* impl */ }
它使用 recursive conditional types, template literal types, and key remapping in mapped types 递归遍历所有属性并将键与中间的 .
连接在一起。我不知道这个特定类型的函数是否适用于 flatten()
的所有用例,我不敢假装它没有潜伏在其中的恶魔。
如果我使用该签名,并使用上面的 const
断言 myObject
调用 flatten(myObject)
,您会得到:
const flattenedMyObject = flatten(myObject);
/* const flattenedMyObject: {
readonly "names.title": "red";
readonly "names.subtitle": "green";
} */
万岁!这足以让 Type
成为 "red" | "green"
:
const returned = [...Object.values(flatten(myObject))] as const
type Type = typeof returned[number] // "red" | "green"
也万岁。
我想如果你真的不关心跟踪键名,你可以通过返回带有 string
index signature:
flatten()
类型签名
type DeepValueOf<T> = object extends T ? object :
T extends object ? DeepValueOf<
T[Extract<keyof T, T extends readonly any[] ? number : unknown>]
> : T
const flatten = <T extends object>(obj: T): { [k: string]: DeepValueOf<T> } => {
/* impl */ }
产生
const flattenedMyObject = flatten(myObject);
/* const flattenedMyObject: {
[k: string]: "green" | "red";
} */
最终
const returned = [...Object.values(flatten(myObject))] as const
type Type = typeof returned[number] // "red" | "green"
我对 DeepValueOf
的担心不如对 Flatten
的担心,但如果没有一些边缘情况,我会感到惊讶。因此,在任何类型的生产代码中使用它之前,您都需要进行真正的测试。