开启React Strict Mode后Next.js打字轮播组件字符丢失求助
React Strict Mode下打字组件字符乱码问题解决方案
问题根源
React Strict Mode在开发环境中会重复执行副作用(useEffect)、状态更新函数,如果组件的打字逻辑依赖定时器且未做防重复处理,多个定时器会同时修改状态,导致字符叠加、丢失或乱序。生产环境Strict Mode不会触发两次渲染,但SSR场景下必须保证开发/生产逻辑一致。
核心解决方案
通过useRef控制副作用执行时机、清理定时器,结合函数式状态更新避免闭包陷阱,具体步骤:
- 用
useRef存储定时器ID,每次副作用执行前清理旧定时器 - 标记首次渲染,跳过Strict Mode下的第二次副作用触发
- 使用函数式状态更新确保基于最新状态修改文本
- 用CSS实现光标闪烁,减少定时器依赖
修改后的组件代码
import { useState, useEffect, useRef } from 'react'; const Typewriter = ({ facts }) => { const [currentText, setCurrentText] = useState(''); const [currentIndex, setCurrentIndex] = useState(0); const [charIndex, setCharIndex] = useState(0); const timerRef = useRef(null); const isFirstRender = useRef(true); useEffect(() => { // Strict Mode下第二次渲染直接跳过副作用 if (!isFirstRender.current) return; isFirstRender.current = false; const typeNextChar = () => { // 函数式更新确保基于最新state拼接字符 setCurrentText(prev => prev + facts[currentIndex][charIndex]); setCharIndex(prev => prev + 1); }; const switchToNextFact = () => { setCurrentIndex(prev => (prev + 1) % facts.length); setCharIndex(0); setCurrentText(''); }; if (charIndex < facts[currentIndex].length) { timerRef.current = setTimeout(typeNextChar, 100); } else { timerRef.current = setTimeout(switchToNextFact, 2000); } // 清理定时器,防止内存泄漏和重复触发 return () => timerRef.current && clearTimeout(timerRef.current); }, [charIndex, currentIndex, facts]); // 组件卸载时重置标记,避免后续重渲染异常 useEffect(() => { return () => isFirstRender.current = true; }, []); return ( <div className="typewriter"> {currentText} <span style={{ animation: 'blink 1s infinite', fontWeight: 'bold' }}>|</span> <style jsx global>{` @keyframes blink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0; } } `}</style> </div> ); }; export default Typewriter;
关键修改说明
- 定时器管理:
timerRef确保每次只保留一个有效定时器,清理时彻底清除旧定时器,避免多实例同时修改状态 - Strict Mode兼容:
isFirstRender标记跳过第二次渲染的副作用执行,防止打字逻辑重复触发 - 状态更新稳定性:函数式更新避免闭包导致的状态滞后问题,保证字符拼接顺序正确
- 光标动画优化:用CSS动画替代定时器控制光标闪烁,减少定时器依赖,避免冲突
内容的提问来源于stack exchange,提问作者AdaCra
相关产品推荐
相关产品推荐

