如何使用映射类型获取对象类型的可选部分?
How to get an optional part of object type with mapped type?
比如我有这个类型
type Foo = {
foo?: number
bar: string
obj?: {
qwe?: number
asd: string
}
}
我想要一个类型
type Foo2 = {
foo: number
obj: {
qwe: number
}
}
我试过了
type OptionalKeys<T> = {[P in keyof T]: T[P] extends undefined ? P : never }[keyof T]
type PickOptionalProperties<T> = {
[P in OptionalKeys<T>]-?: PickOptionalProperties<T[P]>
};
type Foo2 = PickOptionalProperties<Foo>
const o: Foo2 = {
}
但它不起作用,我不确定为什么
问题的第一部分是您的 OptionalKeys
类型。关系是相反的,如果你有一个联合,联合是成员的超类型,而不是相反。例如:
type N = number | undefined extends number ? "Y": "N" // will be "N"
type Y = number extends number | undefined ? "Y": "N" // will be "Y"
所以在我们的例子中,OptionalKeys
将是:
type OptionalKeys<T> = {[P in keyof T]-: undefined extends T[P]? P : never }[keyof T]
我们还需要从键中排除 undefined
,因为由于属性的可选性,它会在那里。
问题的第二部分是如何构造一个在所有情况下都能正确工作的递归类型别名。为此,我们可以转向 DeepReadOnly
示例 here
type Foo = {
foo?: number
fooArray?: number[]
bar: string
obj?: {
qwe?: number
asd: string
}
objArray?: Array<{
qwe?: number
asd: string
}>
}
type OptionalKeys<T> = Exclude<{ [P in keyof T]: undefined extends T[P] ? P : never }[keyof T], undefined>
type primitive = string | number | boolean | undefined | null
type PickOptionalProperties<T> =
T extends primitive ? T :
T extends Array<infer U> ? PickOptionalPropertiesArray<U> :
PickOptionalPropertiesObject<T>
interface PickOptionalPropertiesArray<T> extends ReadonlyArray<PickOptionalProperties<T>> { }
type PickOptionalPropertiesObject<T> = {
readonly [P in OptionalKeys<T>]: PickOptionalProperties<Exclude<T[P], undefined>>
}
type Foo24 = PickOptionalProperties<Foo>
const o: Foo24 = {
foo: 0,
fooArray: [1, 2],
obj: {
qwe: 1,
},
objArray: [
{ qwe: 1 }
]
}
编辑
正如@jcalz 所指出的,这种类型的非严格空检查版本可以使用:
type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T]
其工作方式是测试从他输入的 P
属性 中挑选的内容是否可分配给 {}
,如果是,则表示 属性 P
是可选的,就好像它是必需的一样,结果 Pick
将无法分配给 {}
.
这个版本实际上会更好地挑选出可选属性和 baseType | undefined
类型的必需属性,根据您的用例,我可能会加分或减分。
比如我有这个类型
type Foo = {
foo?: number
bar: string
obj?: {
qwe?: number
asd: string
}
}
我想要一个类型
type Foo2 = {
foo: number
obj: {
qwe: number
}
}
我试过了
type OptionalKeys<T> = {[P in keyof T]: T[P] extends undefined ? P : never }[keyof T]
type PickOptionalProperties<T> = {
[P in OptionalKeys<T>]-?: PickOptionalProperties<T[P]>
};
type Foo2 = PickOptionalProperties<Foo>
const o: Foo2 = {
}
但它不起作用,我不确定为什么
问题的第一部分是您的 OptionalKeys
类型。关系是相反的,如果你有一个联合,联合是成员的超类型,而不是相反。例如:
type N = number | undefined extends number ? "Y": "N" // will be "N"
type Y = number extends number | undefined ? "Y": "N" // will be "Y"
所以在我们的例子中,OptionalKeys
将是:
type OptionalKeys<T> = {[P in keyof T]-: undefined extends T[P]? P : never }[keyof T]
我们还需要从键中排除 undefined
,因为由于属性的可选性,它会在那里。
问题的第二部分是如何构造一个在所有情况下都能正确工作的递归类型别名。为此,我们可以转向 DeepReadOnly
示例 here
type Foo = {
foo?: number
fooArray?: number[]
bar: string
obj?: {
qwe?: number
asd: string
}
objArray?: Array<{
qwe?: number
asd: string
}>
}
type OptionalKeys<T> = Exclude<{ [P in keyof T]: undefined extends T[P] ? P : never }[keyof T], undefined>
type primitive = string | number | boolean | undefined | null
type PickOptionalProperties<T> =
T extends primitive ? T :
T extends Array<infer U> ? PickOptionalPropertiesArray<U> :
PickOptionalPropertiesObject<T>
interface PickOptionalPropertiesArray<T> extends ReadonlyArray<PickOptionalProperties<T>> { }
type PickOptionalPropertiesObject<T> = {
readonly [P in OptionalKeys<T>]: PickOptionalProperties<Exclude<T[P], undefined>>
}
type Foo24 = PickOptionalProperties<Foo>
const o: Foo24 = {
foo: 0,
fooArray: [1, 2],
obj: {
qwe: 1,
},
objArray: [
{ qwe: 1 }
]
}
编辑
正如@jcalz 所指出的,这种类型的非严格空检查版本可以使用:
type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T]
其工作方式是测试从他输入的 P
属性 中挑选的内容是否可分配给 {}
,如果是,则表示 属性 P
是可选的,就好像它是必需的一样,结果 Pick
将无法分配给 {}
.
这个版本实际上会更好地挑选出可选属性和 baseType | undefined
类型的必需属性,根据您的用例,我可能会加分或减分。