【问题标题】:How to get an optional part of object type with mapped type?如何使用映射类型获取对象类型的可选部分?
【发布时间】:2019-02-26 11:46:07
【问题描述】:

比如我有这种类型

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

但它不起作用,我不知道为什么

【问题讨论】:

  • 我可以建议为您的问题添加更多信息吗?语言、语言版本、编辑器.. 以便更好地理解

标签: typescript mapped-types


【解决方案1】:

问题的第一部分在于您的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 类型的必需属性,具体取决于您的使用情况。

【讨论】:

  • 仅供参考,您可以获得带有或不带有 strictNullChecks 的类型的可选键,如下所示:type OptionalKeys&lt;T&gt; = { [K in keyof T]-?: {} extends Pick&lt;T, K&gt; ? K : never }[keyof T]。这将检查事物的“丢失的关键”部分,而不是事物的“可能未定义的值”部分。我会在这里给出自己的答案,但我不想超越你的答案。
  • @jcalz 我的立场是正确的,我在考虑天气 strictNullChecks 真的很需要,因为我今天早上回答了这个问题,但是因为我无法访问,所以没有机会测试想法一台电脑。在严格的空值下肯定更直接。非严格空检查选项的 10 倍,我将其添加到答案中:)
  • 即使有严格的空值检查,上面也可以区分 {a?: string}{a: string | undefined},如果有需要的话......(尽管 Typescript 不能很好地区分那些一般)
  • @jcalz 是的,确实如此,也为此添加了解释。不过,这似乎让一些人感到惊讶,baseType|undefined 类型的属性仍然是必需的,我很确定我在 github 上看到了一张关于它的票 ..
猜你喜欢
  • 2019-01-22
  • 1970-01-01
  • 2019-08-07
  • 1970-01-01
  • 2019-01-21
  • 1970-01-01
  • 2021-11-26
  • 2011-01-12
  • 1970-01-01
相关资源
最近更新 更多