You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>
  );
}

问题原因与修复方案

问题根源

  1. useEffect依赖了text和loading,当text被设置、loading切换为false时,这个useEffect会触发两次:第一次是loading变为false,第二次是text状态更新完成,导致定时器被创建两次,索引被重复递增,跳过了第二个字符。
  2. 依赖外部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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 11:33:16