React中setInterval打字机效果跳过索引1字符的问题排查
React打字机效果跳过第二个字符的问题排查与修复
我在React应用中实现打字机效果,从URL获取字符串后逐个字符显示。预期从第一个字符(索引0)开始依次展示所有字符,但实际运行时跳过了第二个字符(索引1),其余字符显示正常。
当前效果:
预期效果:
补充信息:
- 获取数据后
text状态更新正常 - 已设置每个字符显示间隔为500ms
当前代码:
import "./styles.css"; import React, { useEffect, useState } from "react"; export default function App() { const [text, setText] = useState(""); // 存储获取到的文本 const [displayText, setDisplayText] = useState(""); // 用于打字机效果的文本 const [loading, setLoading] = useState(true); // 加载状态 // 从URL获取文本 useEffect(() => { const fetchText = async () => { try { const response = await fetch("https://example-url.com/text"); // 从URL获取 const data = await response.text(); setText(data); // 将文本存入状态 setLoading(false); // 加载完成 } catch (error) { console.error("Error fetching the text:", error); setLoading(false); } }; fetchText(); }, []); // 打字机效果逻辑 useEffect(() => { if (!loading && text) { let index = 0; const interval = setInterval(() => { setDisplayText((prevText) => prevText + text[index]); index++; if (index === text.length) { clearInterval(interval); // 所有字符显示完成后停止 } }, 500); // 字符显示间隔500ms return () => clearInterval(interval); // 清理函数 } }, [text, loading]); // 渲染带打字机效果的文本 return ( <div className="App"> {loading ? ( <p>Loading...</p> // 加载提示文本 ) : ( <ul> {displayText.split("").map((char, index) => ( <li key={index}>{char}</li> // 逐个字符渲染为列表项 ))} </ul> )} </div> ); }
问题原因与修复方案
问题根源
useEffect依赖了text和loading,当text被设置、loading切换为false时,这个useEffect会触发两次:第一次是loading变为false,第二次是text状态更新完成,导致定时器被创建两次,索引被重复递增,跳过了第二个字符。- 依赖外部
index变量配合异步的setDisplayText,状态更新和索引递增不同步,容易出现字符遗漏。
修复后的代码
import "./styles.css"; import React, { useEffect, useState } from "react"; export default function App() { const [text, setText] = useState(""); const [displayText, setDisplayText] = useState(""); const [loading, setLoading] = useState(true); // 从URL获取文本 useEffect(() => { const fetchText = async () => { try { const response = await fetch("https://example-url.com/text"); const data = await response.text(); setText(data); setLoading(false); } catch (error) { console.error("Error fetching the text:", error); setLoading(false); } }; fetchText(); }, []); // 打字机效果逻辑 useEffect(() => { // 仅在首次加载完成且未开始打字时启动定时器 if (!loading && text && displayText.length === 0) { const interval = setInterval(() => { setDisplayText(prev => { const nextIndex = prev.length; // 字符全部显示完成后清除定时器 if (nextIndex >= text.length) { clearInterval(interval); return prev; } return prev + text[nextIndex]; }); }, 500); return () => clearInterval(interval); } }, [text, loading, displayText]); return ( <div className="App"> {loading ? ( <p>Loading...</p> ) : ( <ul> {displayText.split("").map((char, index) => ( <li key={index}>{char}</li> ))} </ul> )} </div> ); }
关键改进点
- 用
displayText.length作为下一个字符的索引,完全依赖状态自身的长度来定位,避免异步更新带来的偏差 - 添加
displayText.length === 0的判断,确保打字机效果只启动一次,不会因状态重复更新触发多次 - 在
setDisplayText的回调中直接处理终止逻辑,保证字符全部显示后及时清除定时器
内容的提问来源于stack exchange,提问作者Shreya Kolekar
相关产品推荐
相关产品推荐

