如何为合并多对象的动态递归函数正确添加TypeScript类型以获得自动补全提示
解决TypeScript合并端点函数的类型提示问题
嘿,我来帮你搞定这个TypeScript类型提示的问题!你的核心困扰是mergeEndpoints能正确合并对象,但TypeScript没法推断出合并后的具体结构,导致没有自动补全提示。原代码里的泛型尝试没抓对重点,下面给你两种可行的解决方案:
方案一:自动推断合并后的类型(无需手动维护类型)
这种方式让TypeScript自动捕获所有传入端点的类型,递归计算出合并后的完整结构,不用你手动写类型定义:
// 递归合并多个对象类型的工具类型 type Merge<T extends object[]> = T extends [infer First, ...infer Rest] ? First extends object ? Merge<Rest> extends object ? { [K in keyof First | keyof Merge<Rest>]: K extends keyof First ? K extends keyof Merge<Rest> ? MergeDeep<First[K], Merge<Rest>[K]> : First[K] : K extends keyof Merge<Rest> ? Merge<Rest>[K] : never } : First : Merge<Rest> : {}; // 处理嵌套对象的深层合并 type MergeDeep<A, B> = A extends object ? B extends object ? Merge<[A, B]> : B : B; const mergeEndpoints = <T extends object[]>(endpoints: [...T]): Merge<T> => { const result = {} as Merge<T>; const recursiveMerge = (obj: any, entries: [string, any][]) => { for (const [key, value] of entries) { if (typeof value === 'object' && value !== null) { obj[key] = obj[key] ? { ...obj[key] } : {}; recursiveMerge(obj[key], Object.entries(value)); } else { obj[key] = value; } } return obj; }; endpoints.forEach((endpoint) => { recursiveMerge(result, Object.entries(endpoint) as [string, any][]); }); return result; }; // 原端点函数保持不变 const endpointsA = (base: string) => ({ GET: { FEATURE_A: { SOME_PATH: `${base}/featureA`, }, }, }); const endpointsB = (base: string) => ({ GET: { FEATURE_B: { SOME_PATH: `${base}/featureB`, }, }, POST: { FEATURE_B: { SOME_PATH: `${base}/featureB`, }, }, }); const base = '/api'; const endpoints = mergeEndpoints([ endpointsA(base), endpointsB(base), ]); // 现在你能获得完整的自动补全提示了! console.log(endpoints.GET.FEATURE_A.SOME_PATH);
原理说明
Merge工具类型会遍历传入的对象数组,合并所有对象的键,同时处理键冲突时的深层嵌套合并逻辑。MergeDeep负责处理嵌套对象的合并,确保深层结构也能被TypeScript正确推断。- 函数的泛型
<T extends object[]>捕获传入的端点数组类型,通过Merge<T>直接返回合并后的类型,仅在初始化result时做一次类型断言。
方案二:手动定义合并后的类型(你最终采用的方式)
如果需要更明确地控制类型结构,或者你的端点逻辑比较复杂,手动定义合并类型会更可靠:
// 先定义每个端点的具体类型 type A = { GET: { FEATURE_A: { SOME_PATH: string; }; }; }; type B = { GET: { FEATURE_B: { SOME_PATH: string; }; }; POST: { FEATURE_B: { SOME_PATH: string; }; }; }; // 合并所有端点类型 type TEndpoints = A & B; // 修改mergeEndpoints函数,接收泛型T并返回该类型 const mergeEndpoints = <T>(endpoints: Array<Record<string, unknown>>): T => { const result = {} as T; const recursiveMerge = ({ obj, entries }: { obj: Record<string, unknown>; entries: Array<[string, unknown]>; }) => { for (const [key, value] of entries) { if (typeof value === 'object' && value !== null) { obj[key] = obj[key] ? { ...obj[key] } : {}; recursiveMerge({ obj: obj[key] as Record<string, unknown>, entries: Object.entries(value) as Array<[string, unknown]>, }); } else { obj[key] = value; } } return obj; }; endpoints.forEach((endpoint) => { recursiveMerge({ obj: result as unknown as Record<string, unknown>, entries: Object.entries(endpoint) }); }); return result; }; // 给端点函数指定明确的返回类型 const endpointsA = (base: string): A => ({ GET: { FEATURE_A: { SOME_PATH: `${base}/featureA`, }, }, }); const endpointsB = (base: string): B => ({ GET: { FEATURE_B: { SOME_PATH: `${base}/featureB`, }, }, POST: { FEATURE_B: { SOME_PATH: `${base}/featureB`, }, }, }); const base = '/api'; const endpoints = mergeEndpoints<TEndpoints>([ endpointsA(base), endpointsB(base), ]); // 现在同样能获得完整的自动补全! console.log(endpoints.POST.FEATURE_B.SOME_PATH);
原理说明
- 我们给每个端点函数指定了明确的返回类型(
A、B),再通过交集类型TEndpoints = A & B得到合并后的完整类型。 mergeEndpoints函数通过泛型<T>接收这个合并后的类型,将结果断言为T,让TypeScript明确知道返回值的结构,从而提供自动补全提示。
总结
- 如果你不想手动维护类型,方案一的自动推断更适合,它会随着端点函数的变化自动更新类型。
- 如果你需要严格控制类型结构,或者有复杂的类型约束,方案二的手动定义方式更稳定。
内容的提问来源于stack exchange,提问作者Mike K
相关产品推荐
相关产品推荐

