React Router使用Link跳转返回/play路由时Play组件Firestore数据不渲染
核心问题拆解
你观察到的DOM空白、控制台可正常打印数据的现象,本质是两处代码写法错误叠加导致的,和React-Router的Link组件本身无直接关系:
- 完全没有通过setState更新状态,直接修改了state原数组
你在useState解构出users数组后,从来没有调用过对应的setUsers方法,只是在Firestore请求的回调里直接往原数组里push数据。这种操作属于React明确禁止的可变状态修改,完全不会触发组件重渲染。
你能在控制台正常打印users数据,是因为闭包里拿到的数组引用确实被填充了内容,但React内部维护的状态更新机制完全感知不到这个变化,自然不会执行后续的渲染流程。
首次加载页面时能正常显示,是因为刚好撞上了其他流程触发组件重渲染的时机(比如父组件状态更新、路由内置副作用),此时render函数读到了已经被你填充了数据的数组,才展示出正常内容。
第二次回到/play路由时,组件重新挂载,首次渲染会输出加载spinner,之后异步请求回来,你在回调里往闭包的数组里push了数据——因为全程没有调用setUsers,React完全感知不到状态变化,根本不会重新执行render逻辑,这就是为什么连spinner分支都看不到:组件停在首次渲染后的状态,没有任何更新动作。 - 使用随机数作为列表key,引发DOM渲染异常
你在渲染用户列表时用Math.random()作为每个列表项的key,这种不稳定的key会导致React在diff对比虚拟DOM时,无法正确识别已有的DOM节点,极端情况下会把已渲染的内容直接清空。
为什么用a标签跳转就正常
普通a标签会触发整页刷新,整个React应用从头完成初始化挂载,所有状态完全重置,异步请求回来后绕开了你代码里的状态更新bug,自然能正常渲染。这不是a标签比Link功能更强,只是整页刷新的机制刚好掩盖了代码本身的问题。
修复方案
- 改掉可变状态的写法,在请求拿到数据后创建全新的数组引用,通过
setUsers触发React重渲染:useEffect(() => { // 增加卸载标记,避免组件离开后还执行状态更新引发内存泄漏 let isComponentMounted = true; db.collection("users").orderBy("points", "desc").limit(10).get() .then((data: any) => { if (!isComponentMounted) return; const userList: any[] = []; data.forEach((usr: any) => { userList.push(usr.data()); }) // 创建新数组,通过setState触发重渲染 setUsers(userList); }) .catch((err: any) => { console.log("An error has occured: ", err.message); }) // 组件卸载时把标记设为false return () => { isComponentMounted = false; } }, []) - 替换不稳定的随机数key,用用户的唯一标识(比如
username、Firestore文档ID)作为列表项的key:users.map((d: any) => { return ( <div className="userWrapper" key={d.username}> <Link to={`/user/${d.username}`}><img className="lbImage" src={d.profileImage} /></Link> <Link to={`/user/${d.username}`}> <p className="lbUser">{d.username} {d.pro ? <img src={Pro} className="lbPro" /> : null} </p> </Link> <p className="lbPoints">{d.points} Points, {d.rank} ({d.races} Tests)</p> </div> ) })
内容的提问来源于stack exchange,提问作者Tudor Alexandru
相关产品推荐
相关产品推荐

