Angular HttpClient responseType和observe的坑人行为
现在是凌晨2点半。 我从晚上8点就遇到这个问题了,一直想不通,直到刚才查到https://github.com/angular/angular/issues/18586才发现被坑了一回。 问题是这样的,使用Angular的HttpClient发出get请求。文档规定的get方法有2个参数,一个是URL字符串,另外一个是options。在options中可以指定headers、observe、params、reportProgress、responseType、withCredentials。 很自然地就会想到如下方式:把options单独提出来定义 const uri = `${this.config.uri}/${this.domain}`; const httpOptions = { headers: this.headers,responseType: ‘json‘,params: (new HttpParams()).set(‘members_like‘,userId),}; return this.http .get<Project[]>(uri,httpOptions); 可以指定后台返回的数据格式(responseType),比如json格式,text格式,还有blob格式及arraybuffer格式。 如上这种方式看起来是很正常的操作,不过烦人的是tslint报了一个error: 类型“{ headers: HttpHeaders; responseType: string; params: HttpParams; }”的参数不能赋给类型“{ headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: "body"; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType?: "json"; withCredentials?: boolean; }”的参数。 很奇怪对不对?我也很奇怪,明明是正常操作,为什么会报error呢?get方法是支持把responseType设置成json的啊。 百思不得其解,点击get进入angular的代码,显示: get(url: string,options: { headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: ‘body‘; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType: ‘arraybuffer‘; withCredentials?: boolean; }): Observable<ArrayBuffer>; 这个更奇怪了,我明明定义的是json,为什么angular的get方法重载(get方法有15种重载)到arraybuffer呢?观察了好久猜测导航到的重载正好是15种重载中的第一个,是不是ts不能识别这个时候的重载规则所以默认导航到第一个重载了? 按照这个思路想下去,为什么ts不能识别这个时候的重载规则呢?是不是这个时候ts不知道httpOptions?的数据类型啊? 抱着试一试的态度(其实也不抱希望,毕竟连js都有类型推导,更何况ts呢),我给httpOptions?加了数据类型: const uri = `${this.config.uri}/${this.domain}`; const httpOptions: { headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: ‘body‘; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType: ‘json‘; withCredentials?: boolean; } = { headers: this.headers,httpOptions); httpOptions 的数据类型就是直接copy Angular的码源定义的,竟然不报Error了。。。。 结合之前报错的信息“{ headers: HttpHeaders; responseType: string; params: HttpParams; }”,就可以发现: 如果没有给出数据类型,ts会根据规则推导出responseType的数据类型是string,但是get方法的15种重载中,responseType的数据类型是"json" | "text" | "blob" | "arraybuffer"。 string类型显然不是"json" | "text" | "blob" | "arraybuffer"其中的一个,所以报了一个error,那是不是不做类型推导就可以了?为了验证这个猜想,我做了如下测试: const httpOptions: Object = { headers: this.headers,httpOptions); 这样是可以的,考虑一下,数据类型为Object时,就不会再进行类型推导,也就不会报error了。 如果单独把responseType的赋值不提出了呢? const uri = `${this.config.uri}/${this.domain}`; const httpOptions = { headers: this.headers,{...httpOptions,responseType: ‘json‘}); 这样同样也是可以的。所以结论就是: 要么给ts正确的推导,要么不要让ts进行推导。 ? 现在再记录一下上面提到的文章: 观点1: const res = this.http.get(url,{responseType: ‘text‘}); const options = {responseType: ‘text‘}; const res = this.http.get(url,options); 观点2: 可以用下面的方法让ts有正确的推导: const uri = `${this.config.uri}/${this.domain}`;
const httpOptions = {
headers: this.headers,responseType: ‘json‘ as ‘json‘,
params: (new HttpParams()).set(‘members_like‘,};
return this.http
.get<Project[]>(uri,httpOptions);
这样推导出来的就是{responseType: ‘json‘},就可以正常显示了。 观点3: 当然,github上的各位小哥心中都有一个mmp,这个明明是Angular的bug,为什么从4.3版本更新到如今的7.1版本还是没有修改过来? 为了抗议,有个小哥提出如下方法: // define this namespace somewhere export namespace ResponseType { export const JSON = ‘json‘ as ‘json‘; export const ArrayBuffer = ‘arraybuffer‘ as ‘arraybuffer‘; export const Blob = ‘blob‘ as ‘blob‘; export const Text = ‘text‘ as ‘text‘; } // import the namespace above and use it like this const reqOpts = { params: params,headers: headers,responseType: ResponseType.JSON,}; // no type error,the right signature is selected const a = await this.http.get(url,reqOpts); const b = await this.http.get<MyClass>(url,reqOpts); 我尝试了一下,是可以用的。如果只是写成这样,那也不足为奇,小哥本着反正是开源软件,就修改一下源代码吧,Angular的开发人员不修改bug,他就自己修改了。 You would need to first put the following in the declaration @angular/common/http/src/client.d.ts: declare module ‘@angular/common/http‘ { export namespace HttpResponseType { export const JSON: ‘json‘; export const ArrayBuffer: ‘arraybuffer‘; export const Blob: ‘blob‘; export const Text: ‘text‘; } export declare type HttpObserve = ‘body‘ | ‘events‘ | ‘response‘; export namespace HttpObserve { export const Body: ‘body‘; export const Events: ‘events‘; export const Response: ‘response‘; } } Then implement it in angular/packages/common/http/src/: export namespace HttpResponseType { export const JSON: ‘json‘ = ‘json‘; export const ArrayBuffer: ‘arraybuffer‘ = ‘arraybuffer‘; export const Blob: ‘blob‘ = ‘blob‘; export const Text: ‘text‘ = ‘text‘; } export type HttpObserve = ‘body‘ | ‘events‘ | ‘response‘; export namespace HttpObserve { export const Body: ‘body‘ = ‘body‘; export const Events: ‘events‘ = ‘events‘; export const Response: ‘response‘ = ‘response‘; } Finally the two namespaces should be properly exported all the way back to? 我也尝试了一下,也是可以的,但是还是不建议这样修改啊,毕竟没有经过严格的单元测试,不知道会不会影响全局的代码啊。 ? 总结一下:既然官方没有修改,开发者就要一直打补丁,建议使用如下的方式: 1: // 加数据类型 const uri = `${this.config.uri}/${this.domain}`; const httpOptions: { headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: ‘body‘; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType: ‘json‘; withCredentials?: boolean; } = { headers: this.headers,httpOptions); 2: // 把特殊的responseType和observe不要提出来 const uri = `${this.config.uri}/${this.domain}`; const httpOptions = { headers: this.headers,{...httpOptions,responseType: ‘json‘}); 3: // 强制正确类型推导 const uri = `${this.config.uri}/${this.domain}`; const httpOptions = { headers: this.headers,responseType: ‘json‘ as ‘json‘,httpOptions); Over (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |