TypeScript能否为具名参数对象中经包装的匿名函数推断类型?
TypeScript微件工具类类型推断异常问题
我正在用TypeScript编写一个微件(Widget)工具类,核心是维护微件的层级结构,并支持对该结构执行操作,相关类型定义如下:
type WidgetBase = Widget<any, any>; type SubWidgetsBase = {[K: string]: WidgetBase}; type Widget<LoaderResult, SubWidgets extends SubWidgetsBase> = { subWidgets: SubWidgets; loader: (args: { subWidgets: SubWidgets }) => LoaderResult; };
我实现了一个mkWidget函数用于自动推断所有类型,基础场景下能正常工作:
function mkWidget<LoaderResult, SubWidgets extends SubWidgetsBase>(args: { subWidgets: SubWidgets; loader: (args: { subWidgets: SubWidgets }) => LoaderResult; }): Widget<LoaderResult, SubWidgets> { return args; } const w1 = mkWidget({ subWidgets: {}, loader: ((args) => ["w1", args.subWidgets] as const)}); // args.subWidgets 推断类型为 {} const w2 = mkWidget({ subWidgets: { w1 }, loader: ((args) => ["w2", args.subWidgets] as const) // args.subWidgets 推断类型为 { w1: Widget<readonly ["w1", {}], {}> } });
由于这是全栈框架,需要确保部分代码不会被打包到前端,因此使用了wrapLoader宏:该宏在服务端编译时直接展开为传入的函数,在客户端编译为undefined。为了兼容TypeScript,我给它的定义如下:
// wrapLoader(foo) 在服务端会原样展开为 foo,客户端会变成 undefined // 为了让TypeScript通过类型检查,定义如下 function wrapLoader<T>(t: T): T | undefined { return t; } const w3 = mkWidget({ subWidgets: { w1 }, loader: wrapLoader((args) => ["w3", args.subWidgets] as const)! // args.subWidgets 被推断为 never });
此时TypeScript虽然能推断出mkWidget调用时loader参数的类型,却无法将该类型传递给被wrapLoader包装的内联函数,出现如下类型错误:
Type '(args: { subWidgets: never; }) => ["w3", never]' is not assignable to type '(args: { subWidgets: { w1: Widget<readonly ["w1", {}], {}>; }; }) => unknown'
我尝试过以下方法,但均无效:
- 修改包装器返回类型为仅
T:<T>(t: T): T => t - 移除包装器,仅保留
!非空断言 - 在
loader的类型签名中添加NoInfer<SubWidgets>
只有以下两种方法能解决问题,但写法比较繁琐:
- 将
mkWidget拆分为两步调用:mkWidget({ subWidgets })({ loader }) - 使用位置参数:
mkWidget(subWidgets, { loader })
问题疑问
我的代码是否存在问题?如果代码没有问题,这种情况属于TypeScript的预期行为,还是可以提交为Bug?
内容的提问来源于stack exchange,提问作者immerrr
相关产品推荐
相关产品推荐

