TypeScript动态调用方法时扩展运算符类型报错问题咨询
我定义了AndroidInterface接口,描述通过JS桥暴露在window对象的androidInterfaceMethods上的方法签名:
interface AndroidInterface { getXo: () => string | null; getOx: () => string | null; saveXo: (xo: string) => void; saveOx: (ox: string) => void; getGaid: () => string | null; sendAnalyticsEvents: ( eventName: string, eventProperties: Record<string, unknown> ) => void; getAppVersionCode: () => number | null; getClientId: () => string | null; getConfig: () => Record<string, unknown> | null; isFirstAppLogin: () => boolean; logout: () => void; openWhatsapp: (message: string, phone: string) => void; }
同时扩展Window接口:
interface Window{ androidInterfaceMethods?: AndroidInterface; }
编写泛型函数invokeAndroidInterface动态调用这些方法:
const invokeAndroidInterface = <K extends keyof AndroidInterface>( methodName: K, ...args: Parameters<AndroidInterface[K]> ): ReturnType<AndroidInterface[K]> | null => { if (typeof window !== "undefined" && window.androidInterfaceMethods) { const method = window.androidInterfaceMethods[methodName]; if (typeof method === "function") { return method(...args); } } return null }
但method(...args)处报错:A spread argument must either have a tuple type or be passed to a rest parameter. 希望TypeScript自动推断函数签名,不想用类型断言,尝试普通类型守卫无效,硬编码或断言可解决但不够严谨,求合理方案。
解决方案
问题根源在于使用typeof method === "function"时,TypeScript会将method的类型缩小为宽泛的Function类型,丢失了其对应AndroidInterface[K]的具体参数和返回类型信息,导致无法匹配args的Parameters<AndroidInterface[K]>元组类型。
方案1:利用接口约束直接调用
由于AndroidInterface的所有属性都被定义为函数类型,只要window.androidInterfaceMethods符合接口定义,method必然是对应类型的函数。可以移除typeof method === "function"的判断,直接调用:
const invokeAndroidInterface = <K extends keyof AndroidInterface>( methodName: K, ...args: Parameters<AndroidInterface[K]> ): ReturnType<AndroidInterface[K]> | null => { if (typeof window !== "undefined" && window.androidInterfaceMethods) { const method = window.androidInterfaceMethods[methodName]; return method(...args); } return null; };
方案2:自定义类型守卫保留具体类型
如果需要运行时校验方法是否为函数,可编写自定义类型守卫,让TypeScript保留method的具体函数类型:
// 自定义类型守卫,确认方法是对应K的函数类型 function isAndroidInterfaceMethod<K extends keyof AndroidInterface>( method: unknown, _methodName: K ): method is AndroidInterface[K] { return typeof method === "function"; } const invokeAndroidInterface = <K extends keyof AndroidInterface>( methodName: K, ...args: Parameters<AndroidInterface[K]> ): ReturnType<AndroidInterface[K]> | null => { if (typeof window !== "undefined" && window.androidInterfaceMethods) { const method = window.androidInterfaceMethods[methodName]; if (isAndroidInterfaceMethod(method, methodName)) { return method(...args); } } return null; };
两种方案都能让TypeScript自动推断函数签名,无需类型断言,既保证类型安全,又解决了参数展开的报错问题。
内容的提问来源于stack exchange,提问作者Prajwal Kulkarni

