如何获得具有映射类型的对象类型的可选部分?

时间:2018-09-22 07:04:10

标签: typescript mapped-types

例如,我有这种类型

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 = {
}

但是它不起作用,我不确定为什么

1 个答案:

答案 0 :(得分:3)

问题的第一部分是您的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,因为由于属性的可选性而将其放在其中。

问题的第二部分是如何构造一个在所有情况下都能正常工作的递归类型别名。为此,我们可以转到找到hereDeepReadOnly示例

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指出,可以使用以下类型的非严格null检查版本:

 type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T]

这种工作方式是通过测试是否将P类型的{}属性分配给P,这意味着属性Pick是可选的,就像要求结果{}不能分配给baseType | undefined

实际上,此版本将更好地挑选出可选属性以及类型为TextView的必需属性,根据您的使用情况,这些属性可以是加号或减号。