如何用泛型与映射类型捕获TypeScript深度嵌套上下文值?
解决TypeScript嵌套RunConfig中子级context类型推断为any的问题
你的代码里子级fn的context类型被推断为any,核心原因是TChildren泛型定义为Record<string, RunConfig<any, any>>时,any直接切断了TypeScript的递归类型推断链条,导致嵌套的RunConfig类型信息完全丢失。下面分两步解决问题:先修复子级自身context的类型推断,再实现子级继承祖先上下文的进阶需求。
第一步:修复子级自身context的类型推断
重新定义递归的RunConfig类型,移除会中断推断的any,让TypeScript能递归解析嵌套结构的类型:
type RunConfig< TContext extends Record<string, any>, TChildren extends Record<string, RunConfig<any, any>> = {} > = { context: TContext; fn: (arg: { context: TContext }) => any; children: TChildren; }; function run< TContext extends Record<string, any>, TChildren extends Record<string, RunConfig<any, any>> >(config: RunConfig<TContext, TChildren>) { const { context, fn, children } = config; fn({ context }); for (const childConfig of Object.values(children)) { run(childConfig); } } // 测试:子级fn的context类型现在能正确推断 run({ context: { key0: "value0" }, fn({ context }) { // 类型:{ key0: string; } console.log("root", { context }); }, children: { child1: { context: { key1: "value1" }, fn({ context }) { // 类型:{ key1: string; } console.log("child1", { context }); }, children: { child11: { context: { key11: "value11" }, fn({ context }) { // 类型:{ key11: string; } console.log("child11", { context }); }, children: {}, }, }, }, child2: { context: { key2: "value2" }, fn({ context }) { // 类型:{ key2: string; } console.log("child2", { context }); }, children: {}, }, }, });
第二步:让子级接收自身+祖先的合并上下文
如果需要子级fn能访问自身和所有祖先的上下文,需要通过递归类型合并上下文,并在run函数中传递合并后的上下文:
// 合并两个对象类型并扁平化的工具类型 type Merge<T extends Record<string, any>, U extends Record<string, any>> = Omit<T, keyof U> & U; type MergeFlattened<T extends Record<string, any>, U extends Record<string, any>> = { [K in keyof (Omit<T, keyof U> & U)]: (Omit<T, keyof U> & U)[K]; }; type RunConfig< TContext extends Record<string, any>, TChildren extends Record<string, RunConfig<any, any>> = {} > = { context: TContext; // fn的context参数为自身上下文+祖先上下文的合并类型 fn: <Ancestors extends Record<string, any>>(arg: { context: MergeFlattened<Ancestors, TContext> }) => any; children: TChildren; }; function run< TContext extends Record<string, any>, TChildren extends Record<string, RunConfig<any, any>>, TAncestors extends Record<string, any> = {} >(config: RunConfig<TContext, TChildren>, ancestorContext: TAncestors = {} as TAncestors) { const mergedContext = { ...ancestorContext, ...config.context }; config.fn({ context: mergedContext }); // 递归调用时传递合并后的上下文 for (const childConfig of Object.values(config.children)) { run(childConfig, mergedContext); } } // 测试:子级fn能访问自身和所有祖先的上下文 run({ context: { key0: "value0" }, fn({ context }) { console.log("root", context.key0); }, children: { child1: { context: { key1: "value1" }, fn({ context }) { // 类型:{ key0: string; key1: string; } console.log("child1", context.key0, context.key1); }, children: { child11: { context: { key11: "value11" }, fn({ context }) { // 类型:{ key0: string; key1: string; key11: string; } console.log("child11", context.key0, context.key1, context.key11); }, children: {}, }, }, }, child2: { context: { key2: "value2" }, fn({ context }) { // 类型:{ key0: string; key2: string; } console.log("child2", context.key0, context.key2); }, children: {}, }, }, });
内容的提问来源于stack exchange,提问作者Jonathan Sudelko
相关产品推荐
相关产品推荐

