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

RxJS技术实现:确保对象可选属性已定义的操作符需求

问题:实现RxJS操作符notUndefinedKey,保留对象原接口

需求概述

  • 开发RxJS操作符notUndefinedKey,用于检查对象的指定属性是否存在,并修正类型以准确反映该属性已定义的状态
  • 已有针对发射值的notUndefined操作符作为参考:
const notUndefined = <T>() =>
  filter<T | undefined, T>(
    (x): x is T extends undefined ? never : T => x !== undefined
  );
  • 期望使用效果:
interface MyInterface {
  myKey?: string;
}

of<MyInterface>({ myKey: "Some value" }).pipe(
  notUndefinedKey("myKey"),
  map((x) => x.myKey)
  //           ^? (property) myKey: string
)

of<MyInterface>({ myKey: "Some value" }).pipe(
  map((x) => x.myKey)
  //           ^? (property) MyInterface.myKey?: string | undefined
)

现有实现的问题

已完成首个可用版本,但存在明显缺陷:

  • 类型定义冗余复杂
  • 会丢失对象原接口的方法(因Omit+Pick的组合会破坏原类型的方法签名)

现有代码:

import { OperatorFunction, filter, of, map } from "rxjs";

// Typescript types helper
type DefinedFields<TType> = {
  [TKey in keyof TType]-?: DefinedFields<NonNullable<TType[TKey]>>;
};

type WithDefinedKey<TType, TKey extends keyof TType> = Omit<TType, TKey> & DefinedFields<Pick<TType, TKey>>;

type OperatorToDefinedKey<TType, TKey extends keyof TType> = OperatorFunction<
  TType | undefined,
  WithDefinedKey<TType, TKey>
>;

// Operator
export const notUndefinedKey = <TType, TKey extends keyof TType>(
  key: TKey
): OperatorToDefinedKey<TType, TKey> =>
  filter<TType>((x) => x?.[key] !== undefined) as OperatorToDefinedKey<
    TType,
    TKey
  >;

优化后的实现

核心优化思路:使用交叉类型+类型断言函数替代复杂的类型拆解组合,既保留原接口的所有成员(包括方法),又精准缩小类型范围。

import { OperatorFunction, filter } from "rxjs";

/**
 * 类型工具:标记对象的指定属性为非undefined状态
 */
type WithDefinedKey<T, K extends keyof T> = T & { [P in K]-?: NonNullable<T[P]> };

/**
 * RxJS操作符:过滤掉指定属性为undefined的对象,同时修正类型
 */
export const notUndefinedKey = <T, K extends keyof T>(key: K): OperatorFunction<T, WithDefinedKey<T, K>> =>
  filter((obj): obj is WithDefinedKey<T, K> => obj != null && obj[key] !== undefined);

优化点说明

  1. 类型定义简化:WithDefinedKey通过交叉类型直接继承原类型T的所有成员,仅强制指定属性K为非可选、非undefined状态
  2. 完整保留原接口:避免使用Omit和Pick,原接口的方法、其他属性均会被完整保留
  3. 精准类型推断:在filter中直接使用类型断言函数,无需额外类型转换,TypeScript能正确识别后续流中的类型
  4. 增强空值安全:增加obj != null判断,避免对象为null时访问属性导致运行时错误

使用验证

// 包含方法的接口示例
interface User {
  id?: number;
  name?: string;
  greet(): string;
}

of<User>({ id: 1, name: "Alice", greet: () => "Hello" }).pipe(
  notUndefinedKey("id"),
  map(user => {
    // user.id 类型为 number(非可选)
    console.log(user.id.toFixed(0));
    // 仍可调用原接口的方法
    console.log(user.greet());
    return user.name; // name 仍为 string | undefined
  })
);

内容的提问来源于stack exchange,提问作者Mikkel R. Lund

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 23:03:09