【问题标题】:Typescript mapped type, add optional modifier conditionally打字稿映射类型,有条件地添加可选修饰符
【发布时间】:2021-06-29 20:12:14
【问题描述】:

是否可以有条件地将映射类型属性设为可选?

考虑这种类型

type Definition {
  name: string,
  defaultImplementation?: ImplementationType
}

以及他们的记录:

type DefinitionMap = Record<string, Definition>

我想创建一个映射类型,如果提供了输入,则该类型的实现是可选的,但如果没有,则需要映射类型的实现。

对于这样的DefinitionMap

{
  foo: { name: 'x' },
  bar: { name: 'y', defaultImplementation: { /*...*/ } }
}

我想要一个类似的映射类型

{
  foo: ImplementationType,
  bar?: ImplementationType
}

我一直在尝试使用条件并将 undefined 添加到类型中,但这不起作用。

type ImplementationMap<T extends DefinitionMap> = {
  [K in keyof T]: T[K] extends { defaultImplementation: any }
    ? ImplementationType | undefined
    : ImplementationType
}

我知道条件分支的行为符合我的要求,但添加 undefined 实际上并没有使该字段成为可选字段。

【问题讨论】:

    标签: typescript mapped-types


    【解决方案1】:

    我假设DefinitionMap 应该是Record&lt;string, Definition&gt;(而不是Record&lt;string, A&gt;)。

    试试这个:

    // Gets the keys of T whose values are assignable to V
    type KeysMatching<T, V> = {[K in keyof T]: T[K] extends V ? K : never}[keyof T]
    
    type ImplementationMap<T extends DefinitionMap> =
        // A partial (all properties are optional) record for all the keys
        Partial<Record<keyof T, ImplementationType>> &
        // Require ImplementationType for all the keys that do not have defaultImplementation
        Record<KeysMatching<T, { defaultImplementation?: undefined }>, ImplementationType>
    
    /*
    Test is equivalent to
    {
      foo: ImplementationType,
      bar?: ImplementationType,
      baz: ImplementationType
    }
    */
    type Test = ImplementationMap<{
      foo: { name: 'x' },
      bar: { name: 'y', defaultImplementation: { /*...*/ } },
      baz: { name: 'z', defaultImplementaiton: undefined }
    }>
    

    【讨论】:

    • 我比@kaya3 更喜欢这个答案。非常简单。
    • 这确实读起来好多了。但我真正的类型实际上是有趣的泛型本身,所以@kaya3 的答案更加通用。它们实际上基本相同,希望使用 Partial 作为快捷方式。
    【解决方案2】:

    这里有一个解决方案:

    type NonImplementedKeys<T extends DefinitionMap> = {[K in keyof T]: T[K] extends {defaultImplementation: ImplementationType} ? never : K}[keyof T]
    type NiceIntersection<S, T> = {[K in keyof (S & T)]: (S & T)[K]}
    type ImplementationMap<T extends DefinitionMap> = NiceIntersection<{
        [K in NonImplementedKeys<T>]: ImplementationType
    }, {
        [K in keyof T]?: ImplementationType
    }>
    

    例子:

    type DefinitionMapExample = {
      foo: { name: 'x' },
      bar: { name: 'y', defaultImplementation: { /*...*/ } }
    }
    
    // {foo: ImplementationType, bar?: ImplementationType | undefined}
    type ImplementationMapExample = ImplementationMap<DefinitionMapExample>
    

    NiceIntersection&lt;S, T&gt; 类型等效于普通交集类型S &amp; T,除了它使结果看起来像{foo: ..., bar?: ...} 而不是{foo: ...} &amp; {bar?: ...}

    Playground Link

    【讨论】:

      猜你喜欢
      • 2018-09-14
      • 2019-01-05
      • 1970-01-01
      • 2019-11-19
      • 1970-01-01
      • 2022-08-16
      • 1970-01-01
      • 2019-10-25
      相关资源
      最近更新 更多