React倒计时组件触发RandomBoard每秒重渲染问题求助
解决倒计时触发RandomBoard组件重复渲染问题
问题根源
- CountDown组件定时器逻辑缺陷:CountDown中的
useEffect未设置依赖数组,导致组件每次渲染都会重新创建定时器。每秒执行的tick函数会调用settimeleft更新父组件timeleft状态,触发WordSearchContainer重渲染,进而带动RandomBoard跟着重渲染。 - RandomBoard未做渲染优化:即使组件props没有变化,只要父组件重渲染,RandomBoard就会无条件重新渲染。
解决方案
1. 修复CountDown组件的定时器与状态更新逻辑
修改CountDown.js,给useEffect添加正确的依赖数组,同时优化状态更新方式,避免不必要的父组件状态触发:
import React, { useState, useEffect } from 'react'; function CountDown({ hours, minutes, seconds, settimeleft, className, extraclass, over, setOver }) { const [paused, setPaused] = useState(false); const [[h, m, s], setTime] = useState([hours, minutes, seconds]); const tick = () => { if (paused || over) return; if (h === 0 && m === 0 && s === 0) { setOver(true); settimeleft(0); } else { setTime(prev => { const [ph, pm, ps] = prev; let newH = ph, newM = pm, newS = ps; if (pm === 0 && ps === 0) { newH = ph - 1; newM = 59; newS = 59; } else if (ps === 0) { newM = pm - 1; newS = 59; } else { newS = ps - 1; } // 基于当前计算的剩余时间更新父组件状态 settimeleft(newH * 3600 + newM * 60 + newS); return [newH, newM, newS]; }); } }; useEffect(() => { const timerID = setInterval(tick, 1000); return () => clearInterval(timerID); }, [over, paused]); // 仅在over或paused变化时重建定时器 return ( <div className={className}> {!over && <p className={`font-weight-bold ${extraclass}`}>{`Time Left ${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`}</p>} {over && <span className='text-white bg-danger py-2 mb-2 px-2'>Time up </span>} </div> ); } export default CountDown;
2. 给RandomBoard添加渲染优化
使用React.memo包裹RandomBoard组件,只有当props发生变化时才会重渲染:
import React from 'react'; const WordSearchBoard = React.memo(({ setIsStarted }) => { return ( <div className='gameArea mx-4 p-2 row'> {Math.random(9,999)} </div> ); }); export default WordSearchBoard;
3. 优化父组件的副作用逻辑
完善WordSearchContainer的useEffect依赖,避免无效触发:
import React, { useEffect, useState, forwardRef, useImperativeHandle } from 'react' import CountDown from './CountDown'; import WordSearchBoard from './RandomBoard'; const WordSearchContainer = forwardRef(({ timer, data, entryId, hours, minutes, seconds }, ref) => { const [show, setShow] = useState(false); const [timeleft, settimeleft] = useState(timer); const [over, setOver] = useState(false); const [score, setscore] = useState(0); const [isStarted, setIsStarted] = useState(true); // 补充原代码中未定义的函数(需根据业务逻辑实现) const UpdateQuizEntryTimerLeft = () => { // 这里填写更新倒计时的业务逻辑 }; useEffect(() => { if (timer > 0 && timeleft === 0) { settimeleft(0); UpdateQuizEntryTimerLeft(); setTimeout(() => setShow(true), 1000); } }, [timeleft, timer]); // 添加timer依赖,避免无效触发 useImperativeHandle(ref, () => ({ UpdateTimerLeft() { UpdateQuizEntryTimerLeft(); return true; } })); return ( <> {(timer && isStarted) ? ( <CountDown hours={hours} minutes={minutes} seconds={seconds} settimeleft={settimeleft} over={over} setOver={setOver} className='text-center text-danger' extraclass="" /> ) : null} <WordSearchBoard setIsStarted={setIsStarted} /> </> ); }); WordSearchContainer.displayName = "WordSearchContainer"; export default WordSearchContainer;
关键说明
- 给CountDown的
useEffect添加依赖数组,避免每秒重复创建定时器,减少不必要的状态更新。 React.memo会对RandomBoard的props进行浅比较,只有props变化时才触发重渲染,阻止父组件重渲染带来的无效更新。- 优化
settimeleft的调用逻辑,基于组件内部状态的前值计算,避免依赖外部传入的timeleft导致的更新错误。
内容的提问来源于stack exchange,提问作者manish singh
相关产品推荐
相关产品推荐

