React:按不同时机多次渲染组件及解决渲染异常问题
问题描述
我从用户录入的数据库中获取到一个对象数组:
const play = data.plays;
数组结构示例:
// [{time: "30", host: "John", booths: ["1","2","3"]}, // {time: "60", host: "Maria", booths: ["4","5","6"]}, // ...]
启动应用时出现大量警告与错误,比如:
Uncaught TypeError: Cannot read properties of undefined (reading 'length')
修改useEffect的依赖数组后页面能显示,但只会渲染数组的最后一个对象。我尝试用useState管理time、host、box状态,也没得到预期效果。以下是我的Booth组件代码:
function Booth(boxId, label) { let time, host, box, allPlay const play = data.plays; console.log(play) if (play == null) { time = 0 host = '' box = [] } useEffect(() => { allPlay = play.map((play) => { time = play.time host = play.host box = play.booths console.log(time,host,box) let timer = parseInt(time) const currentRender = setInterval (() => { for (let i = 0; i < box.length; i++) { for (let k = 0; k < name.length; k++) { if (box[i] === name[k].value) { const activeBox = document.getElementById(`${box[i]}`) document.getElementById(`time`+`${box[i]}`).innerText = `Time : ` + `${timer}` document.getElementById(`host`+`${box[i]}`).innerText = `Host : ` + `${host}` if (timer > 15) { activeBox.classList.add("red") } else if (timer > 5){ activeBox.classList.add("yellow") } else if (timer > 0){ activeBox.classList.add("green") } else if (timer === 0){ host = '' time = 0 activeBox.className = "white" } } } } if (timer > 0) { timer--; } }, 1000); return () => clearInterval(currentRender); }) },[allPlay]) // react-hooks/exhaustive-deps // but if i change the value of the array, it will re-render the component return ( <div className={`box col-1`}> <h4>{label}</h4> <p className="white" id={`time`+`${boxId}`}>Time:</p> <p className="white" id={`host`+`${boxId}`}>Host:</p> </div> ) } export default Booth;
页面中有多个该组件,希望它们能根据用户输入和时间改变状态,并按不同时机完成渲染。
解决方案
1. 修复组件参数传递错误
React组件接收的是props对象,不是单独的参数,需改为解构写法:
function Booth({ boxId, label }) { // ... 后续代码 }
这样才能正确获取boxId和label属性。
2. 用React状态管理替代DOM直接操作
直接通过getElementById操作DOM违背React状态驱动理念,容易引发渲染冲突。每个Booth组件应独立管理自身状态:
- 用
useState存储倒计时、主持人信息、样式类名 - 无需全局遍历所有booth,组件只关注自身状态变化
3. 重构useEffect逻辑与依赖
原代码中useEffect依赖allPlay为内部赋值变量,会导致依赖错误;循环创建定时器会覆盖之前的变量,最终仅保留最后一个对象的定时器。重构后的组件示例:
import { useState, useEffect } from 'react'; function Booth({ boxId, label, initialTime, initialHost }) { // 组件内部状态 const [timer, setTimer] = useState(parseInt(initialTime) || 0); const [host, setHost] = useState(initialHost || ''); const [boxClass, setBoxClass] = useState('white'); // 倒计时逻辑 useEffect(() => { if (timer <= 0) return; const interval = setInterval(() => { setTimer(prev => { const newTimer = prev - 1; // 根据时间更新样式 if (newTimer > 15) { setBoxClass('red'); } else if (newTimer > 5) { setBoxClass('yellow'); } else if (newTimer > 0) { setBoxClass('green'); } else { setBoxClass('white'); setHost(''); } return newTimer; }); }, 1000); return () => clearInterval(interval); }, [timer]); // 外部数据更新时重置状态(适配用户输入修改) useEffect(() => { setTimer(parseInt(initialTime) || 0); setHost(initialHost || ''); setBoxClass('white'); }, [initialTime, initialHost]); return ( <div className={`box col-1 ${boxClass}`}> <h4>{label}</h4> <p className="white">Time: {timer > 0 ? timer : ''}</p> <p className="white">Host: {host}</p> </div> ); } export default Booth;
4. 父组件正确传递props
在父组件中遍历play数组,为每个booth分配对应初始值:
// 父组件示例 function BoothContainer() { const [plays, setPlays] = useState(data.plays || []); // 此处可添加处理用户输入更新plays的逻辑 // 处理空数据状态 if (!plays || plays.length === 0) { return <div>No booth data available</div>; } return ( <div className="row"> {plays.map((playItem, idx) => { return playItem.booths.map(boxId => ( <Booth key={`booth-${boxId}-${idx}`} boxId={boxId} label={`Booth ${boxId}`} initialTime={playItem.time} initialHost={playItem.host} /> )); })} </div> ); }
关键问题总结
- 组件参数未正确解构,导致
boxId等属性无法获取 - 直接操作DOM违背React状态驱动原则,引发渲染冲突
useEffect依赖错误+循环创建定时器,导致仅最后一个对象生效- 未为每个组件独立管理状态,多个组件共享变量导致覆盖
内容的提问来源于stack exchange,提问作者elsartz
相关产品推荐
相关产品推荐

