React JSX array.map仅遍历首项 初始渲染无显示问题
问题根因
你的代码存在3个核心问题,刚好对应你观察到的三个异常现象:
- 直接修改State原引用,React无法检测更新
你在onCreateRunLocation里写let tempArray = runs,因为数组是引用类型,tempArray和state里的runs指向同一块内存,直接push修改后调用setRuns(tempArray),React浅比较时会发现传入的引用和旧state完全一致,不会触发重渲染。你控制台能看到runs里有3个对象,是因为控制台打印对象时打印的是引用地址,你后续直接修改原数组的操作会同步更新之前打印的日志内容,不代表React认可了这次state更新。 - 渲染逻辑的map没有返回值,页面无法输出内容
你在组件最终return的runs.map回调里用了花括号包裹代码,但是没有returnrenderLocation的执行结果,map最终返回的全是undefined,就算runs里有数据,页面也什么都显示不出来。 - 并发请求触发闭包陷阱,State更新互相覆盖
你在useEffect里同时发起3个数据请求,每个请求的onSnapshot回调触发时,闭包里捕获到的runs都是当次渲染的初始值(也就是空数组)。每个回调执行时都是往自己捕获到的空数组里push1个元素就调用setRuns,后执行的setRuns会直接覆盖前一次的更新结果,最终state里只会保留最后一次更新的1个元素,所以你看到map只遍历第一个元素。加上前面说的直接修改state不触发重渲染,组件render执行两次(初始挂载1次,第一次setRuns触发1次)之后就不会再更新了。 - 其他不规范写法(非核心故障原因):
arrangeProducts和useEffect里错误用map做纯遍历操作,map的设计用途是返回映射后的新数组,纯遍历场景应该用forEach;另外useEffect没有收集onSnapshot的取消订阅函数,组件卸载时会造成内存泄漏。
为什么修改代码热更新后页面就正常?
因为你保存代码触发热更新时,React会强制重渲染组件,此时你之前直接修改的runs数组已经被塞入了3个元素,重渲染时能拿到完整数据,所以页面能正常显示,但这只是歪打正着,首次加载时的逻辑问题依然存在。
修复后代码
状态与数据请求逻辑
const [runs, setRuns] = useState([]) useEffect(() => { // 收集所有订阅取消方法 const unsubscribes = [] machineDocIDs.forEach(item => { unsubscribes.push(fetchProductData(item)) }) // 组件卸载时统一取消订阅,避免内存泄漏 return () => unsubscribes.forEach(unsub => unsub()) },[machineDocIDs]) function fetchProductData(id) { const productsCollectionRef = collection(db, "RunSheets", "All Runs", "Open", runID, "machines", id, "products") const q = query(productsCollectionRef) const productData = [] const IDs = [] const unsubscribe = onSnapshot(q, (querySnapshot) => { querySnapshot.forEach((doc) => { productData.push(doc.data()) IDs.push(doc.id) }) onCreateRunLocation(machines[machineDocIDs.indexOf(id)].machine_name, productData) }) return unsubscribe } function onCreateRunLocation(machine, productData) { const loc = {machine_id: machine, products: arrangeProducts(productData)} // 用函数式更新拿到最新state,返回全新数组,不修改原state setRuns(prevRuns => [...prevRuns, loc]) } function arrangeProducts(productData) { // 直接用map返回映射后的新数组,不需要额外声明数组push return productData.map(item => ({ name: item.flavors, quantity_ordered: item.quantity_ordered })) }
渲染逻辑
function renderProduct(product) { return ( <tr> <th>{product.name}</th> <th>{product.quantity_ordered}</th> </tr> ) } function renderLocation(location) { return ( <Card> <Card.Title>{location.machine_id}</Card.Title> <Card.Body> <Table> <tbody> {location.products.map(item => renderProduct(item))} </tbody> </Table> </Card.Body> </Card> ) } return ( <Container> {/* 箭头函数直接返回渲染结果,列表添加唯一key,实际项目请用item.machine_id代替index */} {runs.map((item, index) => ( <React.Fragment key={index}> {renderLocation(item)} </React.Fragment> ))} </Container> )
内容的提问来源于stack exchange,提问作者Spencer Brereton
相关产品推荐
相关产品推荐

