【问题标题】:TypeScript mapped type to represent an object with optional Observables (with string literal mapped keys)TypeScript 映射类型以表示具有可选 Observables 的对象(使用字符串字面量映射键)
【发布时间】:2022-01-12 07:18:35
【问题描述】:

我需要一个映射类型来表示一个对象,其中属性是可选的 RxJS Observables。

实现这一目标的最简单和最简单的方法是使用这种类型:

type OptionalObservables<T> = {
   [K in keyof T]: T[K] | Observable<T[K]>
}

可以这样使用:

const animal: OptionalObservables<Animal> = {

    species: 'dog',
    noise: of('bark')     // RxJS Observable that satisfies Observable<string>
}
  

(我有一个单独的函数来解析一个对象并订阅 observables。但这个问题并不是关于它的 RxJS 方面。)

但是...对于可观察对象,我通常更喜欢使用 $ 后缀约定,所以我真正想要的是在选择可观察对象时,我希望在键的末尾有一个 $。 Key Remapping 可以进行这样的操作。

{
    species: 'dog',
    noise$: of('bark')  
}

事实证明,让所有东西协同工作比我预期的要困难得多!

对于OptionalObservables&lt;{ species: string, noise: string }&gt;,我最终需要的最终类型是:

// species as a string or observable
({ species: string } | { species$: Observable<string> }) &

// noise as a string or observable
({ noise: string } | { noise$: Observable<string> })

(其中 T 的每个属性 K 都是必需的,并且可以是 T[K] 或 Observable 类型)。


我最接近的基础是这样的:

type OptionalObservables<T> = 
{
   [K in keyof T]: K extends string ? 

                   // the original property name and type
                   { [P in K]: T[K] } 

                   |

                   // OR the original property name with $ suffix and Observable type
                   { [P in `${K}\$`]: Observable<T[K]> }  
                   : never
}

这增加了一个额外的“嵌套”级别(但在其他方面满足了我的要求)所以我最终得到了这个:

{
    species: {
        species: "cat" | "dog";
    } | 
    {
        species$: Observable<"cat" | "dog">;
    };

    noise: {
        noise: "bark" | "meow";
    } | 
    {
        noise$: Observable<"bark" | "meow">;
    };
}

我希望走到这一步可以让我使用Unionize 之类的东西来提取值并将它们合并在一起而无需额外的级别。到目前为止,我只设法获得所有可能的属性是必需的或全部是可选的!

如果有一个我遗漏的技巧或不可能的技巧 - 但希望能够让它发挥作用。

另一种方法可能是验证结构是否与方法兼容,而不是仅依赖于映射类型。

【问题讨论】:

    标签: typescript mapped-types


    【解决方案1】:

    你的要求真的很少见。但这也不是不可能的。

    我们需要在打字稿中使用一些黑魔法。但如你所知,不要轻易使用黑魔法。 ?

    Playground Link

    代码

    type Observable<T> = {value: T}
    
    declare function of<T>(t:T): Observable<T>
    
    type Animal = {
        species: 'species'
        noise: 'noise'
    }
    
    type Unwrap<T> = T extends [any] ? T[0] : T
    
    type SingleOptionalObservables<K extends keyof any, V> = { [T in K]: V } | { [T in K as `${string & T}\$`]: Observable<V> }
    
    type SingleOptionalObservablesByKey<T, K extends keyof T> = K extends any ? [SingleOptionalObservables<K, T[K]>] : never
    
    type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
    
    type OptionalObservables<T> = Unwrap<UnionToIntersection<SingleOptionalObservablesByKey<T, keyof T>>>
    
    type OOA = OptionalObservables<Animal>
    
    // type OOA = ({species: 'species'} | {species$: Observable<'species'>}) & 
    //            ({noise: 'noise'} | {noise$: Observable<'noise'>})
    
    const test1: OOA = {
        species: 'species',
        noise: 'noise',
    }
    
    const test2: OOA = {
        species: 'species',
        noise$: of('noise'),
    }
    
    const test3: OOA = {
        species: 'species',
        noise$: 'noise', // wrong
    }
    
    const test4: OOA = {
        species: of('species'), // wrong
        noise: 'noise',
    }
    

    步骤

    1. 提取类型的键
    2. 将键类型分布式映射到目标联合类型
    3. 将每个部分包装为元组以避免在下一步中对其进行更改
    4. 将联合类型更改为交集类型
    5. 解开元组类型,就是答案

    问题

    虽然我们定义了正确的类型,但 typescript 不支持这种联合类型的交集。仍然允许以下用法。

    const test: OOA = {
        species: 'species',
        noise$: of('noise'),
        noise: 'noise',
    }
    

    我认为 typescript 一定有一个未解决的问题,但我没有找到它。它可能会在未来的版本中得到修复。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-07-27
      • 2019-09-18
      • 1970-01-01
      • 1970-01-01
      • 2018-04-04
      • 1970-01-01
      • 2017-08-08
      相关资源
      最近更新 更多