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

为RxJS自定义useSubscription Hook添加可选泛型select函数

实现带可选select函数的RxJS订阅自定义Hook

问题分析

需要给现有useSubscription Hook添加可选的select转换函数,实现两种场景的类型安全:

  • 无select时,next的参数类型与Observable的泛型T一致
  • 有select时,next的参数类型为select返回的R类型,同时select接收T类型参数

解决方案

通过拆分联合类型+泛型默认值明确类型约束,同时在Hook内部处理Observable的转换逻辑:

1. 定义精确的参数类型

先拆分基础参数类型,再分别定义带/不带select的参数类型,最后合并为联合类型并给泛型R设置默认值:

import { Observable } from 'rxjs';

type BaseParams<T> = {
  observable$: Observable<T> | null;
};

// 带select的参数类型:next接收R类型,select将T转为R
type ParamsWithSelect<T, R> = BaseParams<T> & {
  next: (value: R) => void;
  select: (value: T) => R;
};

// 不带select的参数类型:next接收T类型,select必须不存在
type ParamsWithoutSelect<T> = BaseParams<T> & {
  next: (value: T) => void;
  select?: never;
};

// 最终联合类型,R默认等于T,省略时自动匹配无select场景
type Params<T, R = T> = ParamsWithSelect<T, R> | ParamsWithoutSelect<T>;

2. 修改Hook实现逻辑

在Hook内部判断是否存在select,如果有则用RxJS的map操作符转换Observable,再订阅:

import { useEffect } from 'react';
import { map } from 'rxjs';
// 假设useCallbackRef是你已实现的保持回调引用的自定义Hook
import { useCallbackRef } from './your-hook-path';

// 可选:添加函数重载,让类型推断更直观
export function useSubscription<T>(params: ParamsWithoutSelect<T>): void;
export function useSubscription<T, R>(params: ParamsWithSelect<T, R>): void;

// Hook实现
export function useSubscription<T, R = T>(params: Params<T, R>) {
  const nextCallback = useCallbackRef(params.next);
  const { observable$, select } = params;

  useEffect(() => {
    if (!observable$) return;

    // 根据是否有select转换Observable
    const targetObservable = select 
      ? observable$.pipe(map(select)) 
      : observable$;

    const subscription = targetObservable.subscribe(nextCallback);
    return () => subscription.unsubscribe();
  }, [observable$, nextCallback, select]);
}

3. 使用示例

两种场景都能获得完整的类型提示:

import { of } from 'rxjs';

// 场景1:无select,next参数类型为string
useSubscription({
  observable$: of('Hello RxJS'),
  next: (value) => {
    console.log(value); // value: string
  }
});

// 场景2:有select,next参数类型为number
useSubscription({
  observable$: of('12345'),
  select: (str) => parseInt(str, 10),
  next: (value) => {
    console.log(value); // value: number
  }
});

为什么之前的尝试可能失败

  • 原联合类型没有明确区分select的存在状态,导致TypeScript无法准确推断next的参数类型
  • 未给泛型R设置默认值,导致即使不需要select也必须手动指定R类型
  • 函数重载未正确匹配两种参数类型,导致类型推断失效

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 13:14:59