You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript如何实现支持$后缀可选Observable的自定义映射类型

实现方案

你只需要新增一个常用的 UnionToIntersection 工具类型,把你现有逻辑生成的所有二选一对象联合转换成交叉类型,即可去掉嵌套层级,完整实现如下:

import type { Observable } from 'rxjs'

// TS 通用工具类型:将联合类型转换为交叉类型
type UnionToIntersection<U> = 
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

type OptionalObservables<T> = UnionToIntersection<
  {
    [K in keyof T]: K extends string 
      ? { [P in K]: T[K] } | { [P in `${K}$`]: Observable<T[K]> }
      : never
  }[keyof T] // 提取所有属性值的联合
>

效果验证

用你的示例类型测试:

type Animal = { species: string, noise: string }
type AnimalWithOptionalObservables = OptionalObservables<Animal>

最终推导出来的类型和你期望的完全一致:

({ species: string } | { species$: Observable<string> }) & 
({ noise: string } | { noise$: Observable<string> })

合法/非法用例校验

import { of } from 'rxjs'

// 合法:全原始键
const a1: AnimalWithOptionalObservables = {
  species: 'dog',
  noise: 'bark'
}

// 合法:混合原始键和$后缀键
const a2: AnimalWithOptionalObservables = {
  species: 'dog',
  noise$: of('bark')
}

// 合法:全$后缀键
const a3: AnimalWithOptionalObservables = {
  species$: of('dog'),
  noise$: of('bark')
}

// 非法:同一个属性同时存在原始键和$后缀键
const a4: AnimalWithOptionalObservables = {
  species: 'dog',
  species$: of('dog'),
  noise: 'bark'
}

// 非法:缺少属性
const a5: AnimalWithOptionalObservables = {
  species: 'dog'
}

替代方案(运行时校验)

如果需要更友好的类型提示,也可以用工厂函数封装类型约束,使用体验一致:

function defineObservableObject<T>(input: OptionalObservables<T>) {
  return input
}

// 用法
const animal = defineObservableObject<Animal>({
  species: 'dog',
  noise$: of('bark')
})

内容的提问来源于stack exchange,提问作者Simon_Weaver

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 20:24:01