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

React(NextJS)逐词动画失效问题求助

文本逐词展示动画的时序问题排查与修复

问题描述

要实现网页文本逐词展示的动画效果,采用state变量结合useEffect追加词语:调用useEffect写入第一个词到state,触发useEffect再次执行写入下一个词,渲染时直接展示该state变量。原本设置了间隔控制速度,现已注释用于测试。控制台中可见索引正常递增、词语从数组读取正确,但state变量中丢失第二个词;尝试逐字母实现时也存在第二个索引内容丢失,甚至更多内容混乱的情况。无论设置何种间隔都无法解决,疑似时序问题。

原代码

let index = useRef(0);
const words = text.split(" ");

useEffect(() => {
  const tick = () => {
    console.log("Index: ", index.current);
    console.log("Length: ", words.length);
    console.log("Char: ", words[index.current]);
    console.log("placeholder: ", placeholder);

    setPlaceholder((prev) => prev + " " + words[index.current]);
    index.current++;
  };
  if (index.current < words.length - 1) {
    /* let addChar = setInterval(tick, 70);
    return () => clearInterval(addChar); */
    tick();
  }
}, [placeholder]);

---some other stuff----

return <div>{placeholder}</div>;

问题分析

  1. 竞态条件:依赖placeholder触发useEffect,当setPlaceholder还未完成state更新时,useEffect可能已再次执行,此时index.current已递增,但新的placeholder未生效,导致内容追加逻辑混乱,出现丢词/重复追加的情况。
  2. 边界判断错误:index.current < words.length - 1会导致最后一个词无法被添加,应该判断index.current < words.length。
  3. 无缓存的words:每次渲染都会重新执行text.split(" "),若text动态变化会引发不必要的重渲染,也可能导致依赖项不稳定。

修复方案

方案1:测试版(无间隔,解决时序问题)

import { useState, useEffect, useRef, useMemo } from 'react';

function TypingText({ text }) {
  const [placeholder, setPlaceholder] = useState('');
  const index = useRef(0);
  // 缓存分割后的词语数组,避免重复计算
  const words = useMemo(() => text.split(' '), [text]);

  useEffect(() => {
    const tick = () => {
      if (index.current >= words.length) return;
      
      console.log("Index: ", index.current);
      console.log("Word: ", words[index.current]);
      console.log("Current placeholder: ", placeholder);

      // 函数式更新确保拿到最新的prev值,同时trim()去除开头多余空格
      setPlaceholder(prev => `${prev} ${words[index.current]}`.trim());
      index.current++;
    };

    tick();
  }, [placeholder, words]);

  return <div>{placeholder}</div>;
}

方案2:正式版(带间隔,彻底避免竞态)

不再依赖placeholder触发useEffect,改用定时器主动控制逐词追加,从根源解决时序问题:

import { useState, useEffect, useRef, useMemo } from 'react';

function TypingText({ text }) {
  const [placeholder, setPlaceholder] = useState('');
  const index = useRef(0);
  const intervalRef = useRef(null);
  const words = useMemo(() => text.split(' '), [text]);

  useEffect(() => {
    // 文本变化时重置状态
    setPlaceholder('');
    index.current = 0;
    
    // 启动定时器控制逐词速度
    intervalRef.current = setInterval(() => {
      if (index.current >= words.length) {
        clearInterval(intervalRef.current);
        return;
      }

      setPlaceholder(prev => `${prev} ${words[index.current]}`.trim());
      index.current++;
    }, 70);

    // 组件卸载时清理定时器,避免内存泄漏
    return () => clearInterval(intervalRef.current);
  }, [words]);

  return <div>{placeholder}</div>;
}

关键修复点

  • 用useMemo缓存words数组,避免每次渲染重复分割字符串,提升性能并保证依赖稳定。
  • 正式版改用定时器控制逻辑,不再依赖placeholder触发useEffect,彻底消除竞态条件。
  • 修复边界判断,确保所有词语都能被正确追加。
  • 使用trim()去除开头的多余空格,优化展示效果。
  • 添加定时器清理函数,避免组件卸载后定时器继续运行导致内存泄漏。

内容的提问来源于stack exchange,提问作者Stefan T.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 12:51:56