TypeScript泛型函数类型守卫问题:Axios调用参数校验失效
通用Axios泛型函数的类型校验问题
我在React项目中编写了一个通用的Axios调用泛型函数,用于在仓库层根据不同条件复用,代码如下:
type XHR = 'get' | 'post' | 'delete' function callApi(url: string) { /** will do some logic here */ return <Response = any, Methods = XHR>( methodName: Methods, spec?: Methods extends 'get' ? never : object, ): Promise<Response> => { switch (methodName) { case 'get': return axios.get<Response>(url).then((resp) => resp.data) case 'delete': return axios .delete<Response>(url, { data: { data: spec } }) .then((resp) => resp.data as Response) default: return axios[methodName as Exclude<XHR, 'delete' | 'get'>]<Response>( url, { data: spec, }, ).then((resp) => resp.data) } } }
正常调用方式如下:
let callGoogle = callApi('https://google.com') return callGoogle('get') //or return callGoogle('post', {spec: 'foo'})
但存在一个问题:调用post方法时不传第二个参数,TypeScript并未触发预期的类型错误,示例代码如下:
// 此处应触发TS错误,因为post需要第二个参数 let result2 = callGoogle<{ rambo: number }>('post')
我已在VS Code和TypeScript Playground中测试过上述代码,希望解决该类型校验失效的问题。
解决方案
问题根源在于spec参数的可选标记?,以及泛型参数Methods的约束不够严格。要实现根据请求方法动态要求参数,可通过以下两种方式解决:
方法1:使用函数重载
为不同请求方法定义明确的函数签名,直接区分参数是否必传:
import axios from 'axios'; type XHR = 'get' | 'post' | 'delete'; function callApi(url: string) { // 重载签名:get方法无需第二个参数 function apiCall<Response = any>(methodName: 'get'): Promise<Response>; // 重载签名:post/delete方法必须传入第二个参数 function apiCall<Response = any>(methodName: 'post' | 'delete', spec: object): Promise<Response>; // 通用实现签名 function apiCall<Response = any>(methodName: XHR, spec?: object): Promise<Response> { switch (methodName) { case 'get': return axios.get<Response>(url).then(resp => resp.data); case 'delete': return axios.delete<Response>(url, { data: { data: spec } }).then(resp => resp.data as Response); case 'post': return axios.post<Response>(url, { data: spec }).then(resp => resp.data); default: throw new Error(`Unsupported method: ${methodName}`); } } return apiCall; }
此时调用callGoogle('post')时,TypeScript会直接提示缺少必填的第二个参数。
方法2:优化泛型条件类型
通过元组条件类型严格定义不同方法的参数列表,去掉spec的可选标记:
import axios from 'axios'; type XHR = 'get' | 'post' | 'delete'; // 定义方法对应的参数结构:get对应空元组,post/delete对应含object的元组 type ApiParams<Method extends XHR> = Method extends 'get' ? [] : [spec: object]; function callApi(url: string) { return <Response = any, Method extends XHR = XHR>( ...args: [...ApiParams<Method>] ): Promise<Response> => { const [methodName, spec] = args as [Method, object | undefined]; switch (methodName) { case 'get': return axios.get<Response>(url).then(resp => resp.data); case 'delete': return axios.delete<Response>(url, { data: { data: spec } }).then(resp => resp.data as Response); case 'post': return axios.post<Response>(url, { data: spec }).then(resp => resp.data); default: throw new Error(`Unsupported method: ${methodName}`); } }; }
这种写法通过元组类型约束参数个数,调用post时不传参数会直接触发类型错误。
内容的提问来源于stack exchange,提问作者goju
相关产品推荐
相关产品推荐

