项目中遇到的闭包问题
一、业务场景介绍
点击文件管理,打开上传文件的弹框。
弹框内可以上传文件
即使弹框关闭,上传仍然在继续(上传相当于是一个全局操作,这里是用webWork来做的)
二、问题现象介绍
- 上传文件,正在上传中,关闭弹框。
- 然后重新打开弹框,文件上传成功后,重新请求了list接口,后端返回了正确的数据。
- 但是页面上还是展示的上一次请求回来的数据
三、问题分析
这里先po上我的代码(大概的开发思路,不包含具体的数据处理逻辑)
- 首先是定一个一个useRequest对象
- 然后定义一个onSuccess方法,用于上传文件成功后的操作
// 用于请求 弹框中文件列表 的useRequest对象
const { data: fileList, refresh: fileListRefresh, loading: fileListLoading } = useRequest<(QueryDataSourceFileListRes & TUploadListFile)[]>(async () => {
let res = await QueryDataSourceFileList();
let list = res?.data?.list ?? [];
return list as (QueryDataSourceFileListRes & TUploadListFile)[];
}, {
debounceWait: 500,
});
// 上传成功后执行的方法
const onSeccess = useCallback((f: TUploadListFile) => async res => {
let resAddFile = await AddDataSourceFile();
if (resAddFile.data >= 0) { // 上传文件成功后
message.success("上传成功");
fileListRefresh(); // 调用请求弹框中文件列表请求
}
}, []);
// 将 onSeccess 方法注入到 每一个文件对象中(此处代码略)
以上的代码都是在「弹框组件」中。
回归以及分析问题的过程
- 只有在正在上传时,关闭弹框再打开,才会出现这个问题
- 如果我就在弹框内部,等待文件上传完成就不会出现这个问题
- 而且上传完成后才会有问题,所以第一时间去找onSuccess方法是否存在代码漏洞
- 排查完onSuccess方法后,问题仍然存在
- 找引用此方法的地方,发现正在上传的文件对象里面都被注入了onSuccess方法,而且正在上传的文件对象是Model中的全局对象
- 关闭弹框时,虽然弹框组件销毁了。但是onSuccess方法一直被全局中的文件对象引用着,无法释放,形成了「闭包」
- 所以我们在重新打开弹框后,即使在控制台也看到请求到了最新的数据,但是这时候引用的其实上一次的useRequest对象。
- 导致视图还是展示了上一次useRequest对象的data数据
此时问题已经完成了定位。
问题应该如何解决呢?此时我们只需要将Table组件中的onSucess对象重新赋值就好~
<Table
dataSource={[...(uploadList.find(c => c.key == GUIDKEY + props.dataSourceRecord.id)?.fileList ?? []).map(c => {
c.onSuccess = onSeccess(c); // 为了解决上传数据源时,关闭弹框再打开后,引起的闭包问题导致数据、视图不更新,所以重新将onSuccess方法赋值
return c;
}), ...(fileList ?? [])]}
/>
至此,bugfixed~