使用自定义useCountDown hook触发InputInvestment组件频繁重渲染如何解决
问题根因
- InputInvestment 组件通过
useSelector直接监听全局store中的countdown数值,该数值每秒更新一次,会触发组件强制重渲染,这是频繁重渲染的核心原因。 - 自定义
useCountDownhook存在逻辑冗余:dispatch操作直接写在hook顶层,每次hook渲染都会触发store更新;同时hook内部既维护了本地statecounter,又从store读取countdown,双重维护无必要性,还会增加额外的渲染开销。 - 原hook采用递归setTimeout实现倒计时,每次倒计时更新都会触发hook重渲染,性能不如setInterval实现。
优化方案
根据业务场景不同,有两种优化方向:
场景1:InputInvestment不需要在页面展示倒计时数值,仅需倒计时结束时触发过期逻辑
该场景下完全不需要监听每秒的倒计时数值变化,仅需要监听倒计时结束的状态即可,优化后代码如下:
重构后的useCountDown hook
import { useEffect } from 'react'; import { setTimerCountdown } from 'redux/actions/expirationTimer'; import { useDispatch } from 'react-redux'; const useCountDown = (start, onEnd) => { const dispatch = useDispatch(); useEffect(() => { let counter = start; // 初始化store内的倒计时值 dispatch(setTimerCountdown(counter)); const timer = setInterval(() => { counter -= 1; dispatch(setTimerCountdown(counter)); if (counter <= 0) { clearInterval(timer); // 倒计时结束触发回调 onEnd?.(); } }, 1000); // 组件卸载时清除定时器,避免内存泄漏 return () => clearInterval(timer); }, [start, dispatch, onEnd]); }; export default useCountDown;
重构后的InputInvestment组件
import { useState } from 'react'; const InputInvestment = ({ history, offering, userState, attemptToInvest }) => { const expirationTimer = { EXPIRATION_TIMEOUT: 15, EXPIRATION_INTERVAL: 1000, }; // 仅监听倒计时是否结束,不需要监听每秒数值变化 const [isExpired, setIsExpired] = useState(false); // 传入倒计时结束回调 useCountDown(expirationTimer.EXPIRATION_TIMEOUT, () => setIsExpired(true)); console.log('rerender') if (isExpired) { const title = '投资请求已过期'; const errorMessage = [ <> 长时间未操作,你的投资请求已过期。如果你想投资{offering.title},请<Link to={`/offering/${offering.urlHash}/`}>点击此处</Link>发起新的投资请求。 </>, ]; return ( <NotificationWrapper> <NotificationMessage title={title} textList={errorMessage} /> </NotificationWrapper> ); } // 原有其他业务逻辑 ... };
该方案下,InputInvestment组件仅会渲染2次:第一次初始化渲染,第二次倒计时结束时isExpired状态变更触发渲染,完全避免了每秒的频繁重渲染,同时store内的倒计时数值仍会正常更新,不影响其他组件读取使用。
场景2:InputInvestment需要在页面展示倒计时数值
如果必须要在页面展示每秒变化的倒计时,那对应渲染倒计时的组件重渲染是不可避免的(需要更新视图上的数字),可通过以下方式降低渲染开销:
- 将倒计时展示逻辑抽为独立的小组件,仅让该小组件通过
useSelector监听倒计时数值变化,仅重渲染该小组件,避免父组件InputInvestment和其他子组件跟着重渲染。 - 过期判断逻辑仍沿用场景1的回调方式实现,不要在InputInvestment父组件内监听倒计时数值变化。
内容的提问来源于stack exchange,提问作者jarwin
相关产品推荐
相关产品推荐

