React遍历数据库返回数据更新useState数组时触发无限重渲染报错
问题根因
你遇到的无限重渲染错误完全是因为状态更新的时机不对,核心问题有两个:
- 把
setItems的调用直接写在了组件渲染阶段的JSX map循环里。React的渲染流程是:执行组件函数计算JSX -> 状态变化触发新的一轮渲染。你现在每次渲染执行map时,都会挨个调用setItems触发新的状态更新,新渲染启动后又会跑一遍map、又调用setItems,直接形成无限循环,最终触发React的重渲染次数保护机制。 - 就算解决了循环问题,你现在逐条目追加数组的写法本身也拿不到正确结果:每次调用
setItems([...items, doc.data().id])时,因为闭包缓存的关系,拿到的永远是旧的items值,遍历3个条目最后得到的items只会是最后一个条目的id,根本不是完整列表。
正确实现方案
对于从数据库返回的snapshots派生出的items数组,完全没必要在渲染流程中手动触发状态更新,直接采用派生计算的方式就能解决所有问题:
- 优先直接从snapshots计算需要的items,不需要额外的独立状态:
import { useMemo, useState, useEffect } from 'react'; const [snapshots, setSnapshots] = useState([]); // 直接基于snapshots派生items,snapshots更新时自动重新计算 const items = useMemo(() => { return snapshots.map(doc => doc.data().id); }, [snapshots]); useEffect(()=> { db.collection("users").doc("4sfrRMB5ROMxXDvmVdwL").collection("basket") .get() .then((snapshot) => { setSnapshots(snapshot.docs); }); }, []); return ( <div className="cart__items__item"> {items.map(id => ( <div key={id}>条目ID:{id}</div> ))} </div> )
- 如果后续确实需要对items做独立修改、不能只用派生值,可以在snapshots更新完成后,在对应的effect里一次性完成items的赋值,绝对不要在渲染逻辑里调用setState:
const [snapshots, setSnapshots] = useState([]); const [items, setItems] = useState([]); useEffect(()=> { db.collection("users").doc("4sfrRMB5ROMxXDvmVdwL").collection("basket") .get() .then((snapshot) => { setSnapshots(snapshot.docs); }); }, []); // 仅在snapshots内容变化时,一次性计算并更新items useEffect(() => { if (!snapshots.length) return; const idList = snapshots.map(doc => doc.data().id); setItems(idList); }, [snapshots]);
避坑提醒
- 组件渲染流程必须是纯函数,只能做数据计算、返回JSX,绝对不能在渲染阶段直接调用setState修改状态,这是触发无限重渲染的最常见原因。
- 批量的状态更新要在拿到完整数据源后一次性完成,不要在循环里逐次调用setState,否则不仅会触发多次不必要的重渲染,还会因为闭包缓存拿到旧的状态值,得到错误的计算结果。
内容的提问来源于stack exchange,提问作者Mohammad Amjad
相关产品推荐
相关产品推荐

