TypeScript 中如何在函数间传递数组并填充对象的数组属性?
用 await 取得 getGenericData() 返回的数组,再赋给整个数组属性:
pData.OpenViewModel = await this.getGenericData(
p.pId,
p.reportingDate,
p.OpenViewModel_ST,
p.OpenViewModel_TABLE
);
同时,把外层的 forEach 改成 for...of,让 getRawData() 等待每次请求完成。pData.OpenViewModel[] = ... 不是合法的数组赋值语法,赋值时要去掉属性名后面的 []。
为什么添加 await 后仍然报错?
注意区分以下类型:
| 表达式或声明 | 类型 | 含义 |
|---|---|---|
getGenericData(...) | Promise<GenericViewModel[]> | 等待返回数组的 Promise |
await getGenericData(...) | GenericViewModel[] | 请求完成后得到的数组 |
OpenViewModel[index] | GenericViewModel | 数组中的单个元素 |
getGenericData() 是 async 函数,即使内部已经执行了 await super.post(...),调用它得到的仍然是 Promise。调用方也需要使用 await 才能取得数组。
添加 await 后,如果错误变成:
Type 'GenericViewModel[]' is missing the following properties from type 'GenericViewModel'
说明赋值位置需要一个 GenericViewModel 对象,右侧却提供了整个数组。检查实际代码是否在给某个数组元素赋值、调用 push(array),或者把属性误声明成了 GenericViewModel。
题目中的空下标 OpenViewModel[] 本身就有语法问题,仅凭这一行无法准确判断该类型错误的原因。属性声明和赋值应分别写成:
// 类型声明中的 [] 表示数组
OpenViewModel: GenericViewModel[];
// 给整个数组属性赋值时,不带 []
pData.OpenViewModel = await this.getGenericData(/* 参数 */);
使用 for...of 完成数据填充
下面保留现有的 super.post() 和 this.apiPostResponse.data 取数方式,假定接口返回的数据已经符合相应模型。
原代码混用了 PScorecardModel 和 ProgramScorecardModel,集合属性也有 pcorecardModel 和 pScorecardModel 两种写法。示例统一使用 ProgramScorecardModel 和 pScorecardModel,实际使用时请与项目中的模型名称保持一致。
export interface GenericViewModel {
rating: string;
reportingDate: Date | null;
comments: string;
results: string;
rtg_date: string;
phase: string;
denominator: number;
}
export interface ProgramScorecardModel {
pId: string;
OpenViewModel: GenericViewModel[];
}
export interface ScorecardModel {
pScorecardModel: ProgramScorecardModel[];
}
将以下方法放在原有类中,PViewModel 沿用项目中的定义:
async getRawData(): Promise<ScorecardModel> {
const criteria = {
storedProcedure: 'sp_Get_Data_For_ESM',
};
await super.post(
'universal-sp-pass-thru/spPassThroughnoBody',
criteria,
'api-eprs'
);
const pList: PViewModel[] = this.apiPostResponse.data;
const scorecardData: ScorecardModel = {
pScorecardModel: [],
};
for (const p of pList) {
const pData: ProgramScorecardModel = {
pId: p.pId,
OpenViewModel: [],
};
pData.OpenViewModel = await this.getGenericData(
p.pId,
p.reportingDate,
p.OpenViewModel_ST,
p.OpenViewModel_TABLE
);
scorecardData.pScorecardModel.push(pData);
}
return scorecardData;
}
async getGenericData(
pId: string,
reportingDate: Date | null,
ised: string,
tablename: string
): Promise<GenericViewModel[]> {
const body = {
pId,
reportingDate,
ised,
tablename,
};
const criteria = {
storedProcedure: 'sp_GetScorecard',
body,
};
await super.post(
'sword-universal-sp-pass-thru/spPassThrough',
criteria,
'api-eprs'
);
const items: GenericViewModel[] = this.apiPostResponse.data;
return items;
}
调用方也要等待结果:
const scorecardData = await this.getRawData();
getRawData() 返回组装好的对象后,调用方就可以继续使用。如果项目将结果保存在组件属性中,也可以在循环结束后赋给相应属性。
为什么不能只把 forEach 回调改成 async?
给回调加上 async 后,可以在其中使用 await,但 forEach() 不会等待回调返回的 Promise:
pList.forEach(async (p) => {
// await ...
});
因此,外层的 getRawData() 可能在数据填充完成前就结束。写成 await pList.forEach(...) 也无效,因为 forEach() 返回的是 undefined。
在 for...of 中使用 await,每轮请求会依次完成。当前代码通过共享属性 this.apiPostResponse 读取响应,顺序执行也能避免该循环内多个请求同时写入这个属性。
如果需要并发请求,应先确认每次调用 super.post() 都能直接取得各自的响应,再考虑使用 Promise.all()。它返回 Promise,并不代表这个 Promise 兑现后的值就是响应对象。
替换数组和追加元素怎样选择?
如果接口返回的数据就是该属性的全部内容,直接赋值:
const items = await this.getGenericData(
p.pId,
p.reportingDate,
p.OpenViewModel_ST,
p.OpenViewModel_TABLE
);
pData.OpenViewModel = items;
如果要保留原有元素并追加返回的数据,使用展开语法:
pData.OpenViewModel.push(...items);
不要写成:
pData.OpenViewModel.push(items);
这里的 push() 接收一个或多个 GenericViewModel 元素,items 的类型则是 GenericViewModel[]。
是否需要用 map() 重新构造对象?
如果接口字段已经符合 GenericViewModel,直接返回数组即可。用 map() 逐字段复制后,得到的仍然是 GenericViewModel[],无法解决 Promise 未解包或把数组当成单个元素的问题。
需要重命名字段、转换值或补充数据时,再使用映射。例如,接口通过 JSON 返回日期字符串时,声明 reportingDate: Date | null 不会自动将字符串转换为 Date,仍需按接口约定进行转换。
const items: GenericViewModel[] = this.apiPostResponse.data 也只提供静态类型检查。如果 data 是 any,这条声明无法验证运行时数据。应在接口层为响应定义类型,并按实际需要校验数据。
备注:内容仅供参考。
