React useEffect中setState填充组件后无渲染 仅强制更新生效
技术栈
- 客户端:React、MobX
- 服务端:NodeJS、MongoDB
问题现象
在useEffect内执行元素数组填充逻辑,预期数组内每个元素正常渲染,实际首次加载页面无对应渲染结果,只有VSCode修改代码触发热更新、或者手动点击强制更新按钮后内容才会显示。
已尝试的无效方案:
- 将数组遍历方法从
.map改为.forEach - 尝试
setState(...[arr])等多种展开运算符写法,甚至不使用展开运算符直接传参,问题均未解决
问题根因
核心错误有两处:
- 异步逻辑处理错误:
getUsersToDisplayInFriendRequestsFrom中遍历requestsFrom时使用了async回调,但Array.map本身不会等待内部异步操作执行完成,代码会同步执行到setDisplayRequestsFrom(_arr),此时所有fetchUserDataLite请求都还没返回,_arr是空数组,首次setState会把空数组设置为状态。后续异步请求陆续完成后往_arr里push数据,属于直接修改React状态引用的违规操作,不会触发React重渲染,所以哪怕数组实际已经存了3条数据,页面也不会更新。热更新、手动forceUpdate会强制组件重渲染,此时才能读到已经被修改的数组内容,显示出对应元素。 - useEffect判断逻辑写错:
if (!requestsFrom.length === 0)存在隐式类型转换问题,!requestsFrom.length返回的是布尔值,和0做相等判断永远为false,该分支的return逻辑永远不会命中。
修复方案
- 修正异步遍历逻辑,用
Promise.all等待所有用户数据请求完成,拿到完整结果后再调用setState,禁止直接修改状态引用:
const getUsersToDisplayInFriendRequestsFrom = async () => { const userList = await Promise.all( requestsFrom.map(async (f) => { const _candidate = await userPage.fetchUserDataLite(f) return { isRequest: true, link: '#', username: _candidate.login, userId: _candidate._id } }) ) setDisplayRequestsFrom(userList) }
- 修正useEffect中的错误判断,调整逻辑顺序,避免在同一次effect中依赖刚更新的状态:
useEffect(() => { console.log(`requestsFrom.length === ${requestsFrom.length}`) if (requestsFrom.length === 0) { const friendRequests = toJS(friend.requests.from) setRequestsFrom(friendRequests.length > 0 ? friendRequests : ['0']) return } if (displayRequestsFrom.length < 1) { getUsersToDisplayInFriendRequestsFrom() } }, [requestsFrom, displayRequestsFrom])
注意事项
- 不要在异步回调中直接修改提前拷贝的状态数组,所有状态更新必须通过对应的setState函数触发,且要保证传入setState的是完整的新值
- 写条件判断时避免
!xxx === 0这类存在隐式类型转换的歧义写法,直接判断目标值即可,减少逻辑bug
内容的提问来源于stack exchange,提问作者mortvicious
相关产品推荐
相关产品推荐

