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

为何TypeScript高阶函数返回类型不正确?

解决TypeScript中useTestSubColRef根据参数返回正确类型的问题

问题描述

我需要让高阶函数useTestSubColRef根据传入的参数返回不同类型,但遇到了TypeScript类型推断错误:

  • 调用useTestSubColRef("docId")或useTestSubColRef("docId", true)时,返回值类型应为CollectionReference<WithId<SerpLocalPackItem>>,但实际被推断为CollectionReference<SerpLocalPackItem>(缺少WithId包装)
  • 仅调用useTestSubColRef("docId", false)时类型推断正确

原代码如下:

import { CollectionReference, DocumentData } from "firebase/firestore";
// 假设WithId、col、SerpLocalPackItem已定义

const subColRefWithId = <T extends DocumentData>(
  collectionName: string,
  docId: string,
  subCollectionName: string,
): CollectionReference<WithId<T>> => {
  return col<WithId<T>>(collectionName, docId, subCollectionName);
};

const subColRef = <T extends DocumentData>(
  collectionName: string,
  docId: string,
  subCollectionName: string,
): CollectionReference<T> => {
  return col<T>(collectionName, docId, subCollectionName);
};

export const useTestSubColRef = (docId: string, withId: boolean = true) => {
  return withId
    ? subColRefWithId<SerpLocalPackItem>("colName", docId, "subColName")
    : subColRef<SerpLocalPackItem>("colName", docId, "subColName");
};

// test1 应为 CollectionReference<WithId<SerpLocalPackItem>>,但显示为 CollectionReference<SerpLocalPackItem>
const test1 = useTestSubColRef("docId");

// test2 应为 CollectionReference<WithId<SerpLocalPackItem>>,但显示为 CollectionReference<SerpLocalPackItem>
const test2 = useTestSubColRef("docId", true);

// test3 类型正确
const test3 = useTestSubColRef("docId", false);

问题原因

原函数未明确不同参数组合对应的返回类型,TypeScript会自动合并两个分支的返回类型为联合类型CollectionReference<WithId<SerpLocalPackItem>> | CollectionReference<SerpLocalPackItem>。由于WithId<T>本质是T的扩展类型(如T & { id: string }),TypeScript会将其简化为更宽泛的CollectionReference<SerpLocalPackItem>,导致类型推断不符合预期。

解决方案

方案一:使用函数重载

通过定义多个重载签名,明确不同参数组合对应的返回类型:

import { CollectionReference, DocumentData } from "firebase/firestore";

const subColRefWithId = <T extends DocumentData>(
  collectionName: string,
  docId: string,
  subCollectionName: string,
): CollectionReference<WithId<T>> => {
  return col<WithId<T>>(collectionName, docId, subCollectionName);
};

const subColRef = <T extends DocumentData>(
  collectionName: string,
  docId: string,
  subCollectionName: string,
): CollectionReference<T> => {
  return col<T>(collectionName, docId, subCollectionName);
};

// 重载签名:明确不同参数下的返回类型
export function useTestSubColRef(docId: string): CollectionReference<WithId<SerpLocalPackItem>>;
export function useTestSubColRef(docId: string, withId: true): CollectionReference<WithId<SerpLocalPackItem>>;
export function useTestSubColRef(docId: string, withId: false): CollectionReference<SerpLocalPackItem>;

// 函数实现
export function useTestSubColRef(docId: string, withId: boolean = true) {
  return withId
    ? subColRefWithId<SerpLocalPackItem>("colName", docId, "subColName")
    : subColRef<SerpLocalPackItem>("colName", docId, "subColName");
}

// 现在类型推断全部正确
const test1 = useTestSubColRef("docId"); // CollectionReference<WithId<SerpLocalPackItem>>
const test2 = useTestSubColRef("docId", true); // CollectionReference<WithId<SerpLocalPackItem>>
const test3 = useTestSubColRef("docId", false); // CollectionReference<SerpLocalPackItem>

方案二:使用泛型+条件类型

通过泛型捕获withId的字面量类型,结合条件类型返回对应结果:

import { CollectionReference, DocumentData } from "firebase/firestore";

const subColRefWithId = <T extends DocumentData>(
  collectionName: string,
  docId: string,
  subCollectionName: string,
): CollectionReference<WithId<T>> => {
  return col<WithId<T>>(collectionName, docId, subCollectionName);
};

const subColRef = <T extends DocumentData>(
  collectionName: string,
  docId: string,
  subCollectionName: string,
): CollectionReference<T> => {
  return col<T>(collectionName, docId, subCollectionName);
};

export const useTestSubColRef = <T extends boolean = true>(
  docId: string,
  withId: T = true as T
): T extends true 
  ? CollectionReference<WithId<SerpLocalPackItem>> 
  : CollectionReference<SerpLocalPackItem> => {
  return (withId
    ? subColRefWithId<SerpLocalPackItem>("colName", docId, "subColName")
    : subColRef<SerpLocalPackItem>("colName", docId, "subColName")) as any;
};

// 类型推断全部正确
const test1 = useTestSubColRef("docId"); // CollectionReference<WithId<SerpLocalPackItem>>
const test2 = useTestSubColRef("docId", true); // CollectionReference<WithId<SerpLocalPackItem>>
const test3 = useTestSubColRef("docId", false); // CollectionReference<SerpLocalPackItem>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 15:05:31