Angular18升级后API回调参数偶现undefined问题成因咨询
问题背景
将Angular从16升级至18.2.13并切换构建模式为angular-devkit/build-angular:application后,调用后端API的QuickRenew函数中,receiveCallBack执行时偶现resHubDto为undefined,但后端明确返回了有效值。将responseData = res修改为responseData = {...res}后问题解决,需分析具体成因。
相关代码
QuickRenew函数
public QuickRenew = (requestData: myRequestHUb, receiveCallBack: (data: myREsHub) => void, receiveErrorCallBack: (data: myREsHub) => void, noSessionCallBack: (data: myREsHub) => void, completeCallBack: (data: myREsHub) => void = null, receiveUnExceptedErrorCallBack: (data: myREsHub ) => void, reciveSubMaintenanceModeCallBack:(data: myREsHub ) => void): void => { const url = location.protocol + "//" + location.host + '/api/QuickHub/Quickrenew'; let responseData: myREsHub = null; this.http.post<any>(url, requestData, { responseType: "json", headers: { "X-XSRF-TOKEN" : checkToken } }) .subscribe( (res: any) => { responseData = res; if (!!responseData.MaintenanceMode) { location.href = "/"; return; } if (responseData.NoSession) { noSessionCallBack(responseData); } else if (responseData.IsUnexpectedError) { receiveUnExceptedErrorCallBack(responseData); } else if (responseData.IsError) { receiveErrorCallBack(responseData); } else if (responseData.SubMaintenanceMode){ reciveSubMaintenanceModeCallBack(responseData); }else { receiveCallBack(responseData); } }, e => { receiveUnExceptedErrorCallBack(responseData); }, () => { console.log("success") if (!!completeCallBack) { completeCallBack(responseData); } } ); }
receiveCallBack函数
async (resHubDto: myResponse) => { console.log("implement start") const transferData = { invoiceId: resHubDto.InvoiceId, }; this.serviceContainer.DataTransferService.setData(transferData, this.serviceContainer.DataTransferService.QuickDomainRenewDoneKey); this.router.navigate(['/sp/quick/renewal/done']); }
依赖版本
"dependencies": { "@angular/animations": "^18.2.13", "@angular/cdk": "^18.2.14", "@angular/common": "^18.2.13", "@angular/compiler": "^18.2.13", "@angular/core": "^18.2.13", "@angular/forms": "^18.2.13", "@angular/platform-browser": "^18.2.13", "@angular/platform-browser-dynamic": "^18.2.13", "@angular/router": "^18.2.13", "@ngrx/store": "^18.1.1", "@ngrx/store-devtools": "^18.1.1", "@types/credit-card-type": "^9.0.0", "@types/file-saver": "^2.0.7", "@types/pikaday": "^1.7.9", "card-validator": "^8.1.1", "chart.js": "^4.4.3", "core-js": "^3.36.1", "credit-card-type": "^10.0.0", "dompurify": "^3.2.4", "esbuild": "^0.25.2", "file-saver": "^2.0.5", "gulp-esbuild": "^0.14.0", "lodash": "^4.17.21", "marked": "^15.0.7", "ng2-charts": "^6.0.1", "path-to-regexp": "^8.2.0", "pikaday": "^1.8.2", "rxjs": "^6.6.7", "trim-newlines": "^5.0.0", "tslib": "^2.6.2", "zone.js": "~0.14.4" },
成因分析
Angular 18 HTTP客户端底层实现变更
Angular 18默认使用fetch替代了传统的XMLHttpRequest作为HTTP客户端的底层实现,在application构建模式下这一变更的影响更显著。fetch序列化响应后返回的是Proxy代理对象,而非普通JS对象。当receiveCallBack是异步函数时,代理对象可能因内部引用机制被过早回收,导致访问属性时出现undefined。浅拷贝的修复原理
使用{...res}进行浅拷贝,会提取代理对象的所有可枚举属性,生成一个独立的普通JS对象。这个对象脱离了原代理的引用链,不会被提前回收,能稳定保留后端返回的数据,因此解决了undefined问题。新构建模式的优化放大问题
angular-devkit/build-angular:application是Angular 17+引入的新构建模式,默认启用了更多激进优化(如深度树摇、代理对象内存优化),使得原本在旧构建模式下不明显的代理对象引用问题暴露出来。而Angular 16使用的旧构建模式中,HTTP响应返回的是普通对象,因此无此问题。TypeScript版本的间接影响
Angular 18配套的TypeScript 5.x版本对类型检查和对象处理更严格,代理对象与自定义类型myREsHub/myResponse的兼容性问题被放大,间接导致了偶现的undefined情况,但这并非直接成因。
内容的提问来源于stack exchange,提问作者công

