为何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
相关产品推荐
相关产品推荐

