出问题的代码:
console.log('rerender')
useEffect(() => {
if (Array.isArray(selectedIds)) {
// 只有初始化时会实际请求接口,之后通过人员选择组件的回调更新
// 直接拉取缓存数据时,value + 异步设置 Users ,共触发了两次rerender,可以接受
userBaseInfoModel.getUsers(selectedIds).then((res)=>{
setUsers([...res]);
console.log(JSON.stringify(res));
});
}
}, [value]);Ï
userBaseInfoModel.getUsers是一个可缓存的数据访问接口(最后确实是这里出了问题)
体现
输出、vsc debug都没有发现问题
- useEffect触发了3+次
- log最后一次数据返回,没有rerender
- []
- rerender
- []
- rerender
- [有数据]
- devtools中组件最终state的值是 []
setState没有正确更新state的值,这冲击了我对react基本原理的认知
定位
经过对外部的排查,没有发现问题,最终将范围缩小到了:
- antD table columns render的实现问题
- userBaseInfoModel.getUsers的返回有问题
搜索1发现了一些似是而非的issues,基本上都是数据依赖的基础使用问题,没啥用
想办法调试2,发现了一些端倪
const promise = userBaseInfoModel.getUsers(selectedIds);
console.log(promise);
// ......
最终输出的结果发现log:
- Promise {<pending>}
- Promise {<fulfilled>: Array(0)}
- Promise {<fulfilled>: Array(0)}
- Promise {<fulfilled>: Array(0)}
- ...
在一堆log里面隐约发现了问题,有数据的返回虽然最后调用了setState设置了正确数据,但是他的 useState 出发在更早以前,综合之前的发现,可能和组件刷新复用有关,类似于 useEffect 应该在 return 里 cancel 异步,这里因为没有实现 cancel 导致 异步中的 log 输出混乱影响了判断
- useEffect cancel订阅很重要
- Suspense 在无其他库依赖时的使用还需要测试
解决
最终修改 userBaseInfoModel.getUsers 内部逻辑,正确解决了问题
对比
// 修改前
getUsers(ids: number[]) {
let anyFetch = false;
for (const id of ids) {
if (id && !this.pushedIdSet.has(id)) {
this.requestQueue.add(id);
this.pushedIdSet.add(id);
anyFetch = true;
}
}
// 需要return实际请求这批数据的promise
if (anyFetch) {
return this.trigger().then(() => this.getListWithIds(ids));
} else {
return Promise.resolve(this.getListWithIds(ids));
}
}
// 修改后
async getUsers(ids: number[]) {
let anyFetch = false;
for (const id of ids) {
if (id && !this.pushedIdSet.has(id)) {
this.requestQueue.add(id);
this.pushedIdSet.add(id);
}
if (!this.store.has(id)) {
anyFetch = true;
}
}
// 需要return实际请求这批数据的promise
// 存在重复渲染的情况,短时间内多次请求了相同id,每一次都要将当前任务返回
if (anyFetch) {
await this.trigger();
}
return this.getListWithIds(ids);
}