【问题标题】:TypeScript conditional types - filter out readonly properties / pick only required propertiesTypeScript 条件类型 - 过滤掉只读属性/只选择需要的属性
【发布时间】:2019-06-06 17:10:46
【问题描述】:

使用 TypeScript 中的新条件类型(或者可能是另一种技术),有没有办法根据其修饰符从接口中仅选择某些属性?例如,拥有...

interface I1 {
    readonly n: number
    s: string
}

我想在前一个的基础上创建一个新类型,如下所示:

interface I2 {
    s: string
}

【问题讨论】:

    标签: typescript typescript2.8 conditional-types


    【解决方案1】:

    2018 年 10 月更新:@MattMcCutchen 发现它可以检测到readonly 字段(使下面被删除的段落无效),如this answer 所示。这是构建它的一种方法:

    type IfEquals<X, Y, A=X, B=never> =
      (<T>() => T extends X ? 1 : 2) extends
      (<T>() => T extends Y ? 1 : 2) ? A : B;
    
    type WritableKeys<T> = {
      [P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P>
    }[keyof T];
    
    type ReadonlyKeys<T> = {
      [P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, never, P>
    }[keyof T];
    

    如果要从接口中提取可写字段,可以将上面的WritableKeys定义和Pick一起使用:

    interface I1 {
        readonly n: number
        s: string
    }
    
    type I2 = Pick<I1, WritableKeys<I1>>; 
    // equivalent to { s: string; }
    

    万岁!

    对于readonly,我认为您无法提取这些内容。我有looked at this issue before,当时不可能;而且我认为没有任何改变。

    由于the compiler doesn't soundly check readonly properties,您始终可以将{readonly n: number} 分配给{n: number},反之亦然。因此明显的 TSv2.8 条件类型检查不起作用。例如,如果{n: number} 不被视为可分配给{readonly n: number},那么您可以执行以下操作:

    // does not work, do not try this
    type ExcludeReadonlyProps<T> = Pick<T,
      { [K in keyof T]-?:
        ({ readonly [P in K]: T[K] } extends { [P in K]: T[K] } ? never : K)
      }[keyof T]>
    
    type I2 = ExcludeReadonlyProps<I1> // should be {s: string} but is {} ?
    

    但你不能。在GitHub issue originally named "readonly modifiers are a joke" 中有一些有趣的讨论。

    对不起!祝你好运。


    对于可选属性,您确实可以检测到它们并因此提取或排除它们。这里的见解是{} 扩展了{a?: string},但{} 没有扩展{a: string} 甚至{a: string | undefined}。以下是如何构建一种从类型中删除可选属性的方法:

    type RequiredKeys<T> = { [K in keyof T]-?:
      ({} extends { [P in K]: T[K] } ? never : K)
    }[keyof T]
    
    type OptionalKeys<T> = { [K in keyof T]-?:
      ({} extends { [P in K]: T[K] } ? K : never)
    }[keyof T]
    
    type ExcludeOptionalProps<T> = Pick<T, RequiredKeys<T>>
    
    type I3 = { 
      a: string, 
      b?: number, 
      c: boolean | undefined
    }
    
    type I4 = ExcludeOptionalProps<I3>;
    // {a: string; c: boolean | undefined} ?
    

    所以这很好。


    最后,我不知道您是否希望能够使用 publicprivateprotectedabstract 等纯类属性修饰符来做一些事情,但我会怀疑。碰巧privateprotected 类属性可以很容易排除,因为它们在keyof 中不存在:

    class Foo {
      public a = ""
      protected b = 2
      private c = false
    }
    type PublicOnly<T> = Pick<T, keyof T>; // seems like a no-op but it works
    type PublicFoo = PublicOnly<Foo>; // {a: string} ?
    

    但是提取privateprotected 属性可能是不可能的,因为排除它们非常容易:keyof Foo 没有它们。对于包括abstract 在内的所有这些,您不能将它们添加到类型别名中的属性中(它们是仅限类的修饰符),因此我想不出太多的办法来触摸它们。


    好的,希望对您有所帮助。

    【讨论】:

    • 是的,我也在看这个,不要认为有办法基于只读创建任何类型的不兼容
    • 非常有趣,非常感谢。现在我又想到了两个小问题:1)如果像--enforceReadonlyAssignability 这样的东西被添加到编译器中,你认为你的类型会起作用吗? 2)其他修饰符呢,例如可选修饰符??
    • 万一以后有人发现这个帖子,有 a way to check for readonly fields 滥用条件类型的可分配性规则。 (我想理论上我应该在这里发布一个答案,因为这个问题是最受欢迎的,然后将另一个问题作为重复而关闭?我不会被打扰。)
    • @MattMcCutchen 哇,太棒了!我会编辑这个答案。
    • @spenceryue 我看到编辑已被其他人拒绝,但我想解释一下为什么-? 是必要的:在--strictNullChecks 模式下,可选属性会自动附加到它们的| undefined值类型。由于过滤器是homomorphic mapped type,默认行为是将可选属性转换为可选属性。假设您希望过滤器生成类似 "foo" | "bar" 而不是 "foo" | "bar" | undefined 的内容,则需要显式删除可选修饰符。
    猜你喜欢
    • 2018-09-13
    • 1970-01-01
    • 2011-11-08
    • 1970-01-01
    • 1970-01-01
    • 2010-11-22
    • 2020-05-03
    相关资源
    最近更新 更多