React中用setTimeout延迟状态更新实现过渡效果是否合理?
你的实现方式存在的问题
- 强耦合风险:你写的300ms延迟和CSS动画时长硬绑定了,以后要是改动画时长,必须同步改这个数字,很容易漏改出bug
- 时机不精准:如果浏览器卡了、渲染慢了,动画实际完成时间可能和300ms不符,这时候模态框的显示时机就会和OutterNav的动画脱节,视觉上会不协调
更靠谱的方案:监听CSS动画结束事件
不用靠猜时间,直接等OutterNav的淡出/缩放动画真正结束后,再更新state显示模态框,完全和动画时长解耦。
修改后的代码示例
import React, { useRef, useCallback } from "react"; const OutterNav = ({ setShowInnerNav }) => { const containerRef = useRef(null); const handleAnimationEnd = useCallback((e) => { // 只处理我们关心的过渡属性(比如透明度或缩放) if (e.propertyName.includes('opacity') || e.propertyName.includes('transform')) { setShowInnerNav(true); // 用完就移除监听,防止重复触发 containerRef.current?.removeEventListener('transitionend', handleAnimationEnd); } }, [setShowInnerNav]); const handleClick = useCallback(() => { const container = containerRef.current; if (!container) return; container.classList.add("search-widget-container-animation"); // 绑定过渡结束事件 container.addEventListener('transitionend', handleAnimationEnd); }, [handleAnimationEnd]); return ( <div className='search-widget-container' ref={containerRef} onClick={handleClick} > <button className='btn'> <span className='btn-text'>Anywhere</span> </button> <button className='btn'> <span className='btn-text'>Any week</span> </button> </div> ); }; export default OutterNav;
更贴合React风格的优化:用状态管理动画类
不想直接操作DOM的话,可以用React的状态来控制动画类的添加,更符合React的数据流逻辑:
import React, { useState, useCallback } from "react"; const OutterNav = ({ setShowInnerNav }) => { const [isAnimating, setIsAnimating] = useState(false); const handleAnimationEnd = useCallback((e) => { if (e.propertyName.includes('opacity') || e.propertyName.includes('transform')) { setShowInnerNav(true); setIsAnimating(false); } }, [setShowInnerNav]); const handleClick = useCallback(() => { setIsAnimating(true); }, []); return ( <div className={`search-widget-container ${isAnimating ? 'search-widget-container-animation' : ''}`} onClick={handleClick} onTransitionEnd={handleAnimationEnd} > <button className='btn'> <span className='btn-text'>Anywhere</span> </button> <button className='btn'> <span className='btn-text'>Any week</span> </button> </div> ); }; export default OutterNav;
注意点
- 如果你的动画是用
@keyframes写的,就把transitionend换成animationend事件 - 判断
propertyName是为了避免其他无关的过渡效果触发回调 - 用
useCallback包裹事件函数,避免组件每次重渲染都重新绑定事件
内容的提问来源于stack exchange,提问作者b_meet
相关产品推荐
相关产品推荐

