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

React中3个容器的顺序打字效果实现需求及优化问询

React 实现逐容器顺序打字效果的简洁方案

需求说明

需要实现三个并排容器的打字效果:从最左侧容器开始,完成该容器的字符串打字后,再依次启动后续容器,最终每行展示time、message、eventid三个字段,按顺序完成每行的打字后再处理下一行。

简洁实现方案

核心思路

  • 用二维数组typingProgress记录每一行每个字段的已输入字符长度
  • 按「行→字段」的顺序逐字符打字,完成一个字段后切换到下一个字段,完成一行后切换到下一行
  • 定时器自动处理打字进度,完成所有内容后自动终止

完整代码实现

import { useState, useEffect } from 'react';
import classes from './YourComponent.module.css'; // 替换为你的样式文件

const TypingLog = ({ log }) => {
  // typingProgress:二维数组,[行索引][字段索引] → 已输入字符长度
  // 字段索引:0=time,1=message,2=eventid
  const [typingProgress, setTypingProgress] = useState([]);
  const [currentRow, setCurrentRow] = useState(0); // 当前正在打字的行
  const [currentField, setCurrentField] = useState(0); // 当前正在打字的字段

  useEffect(() => {
    if (!log || log.length === 0) return;

    // 初始化进度数组:每一行的三个字段初始长度都是0
    if (typingProgress.length === 0) {
      setTypingProgress(log.map(() => [0, 0, 0]));
    }

    const interval = setInterval(() => {
      setTypingProgress(prev => {
        const newProgress = [...prev];
        const fieldNames = ['time', 'message', 'eventid'];
        const targetField = fieldNames[currentField];
        const targetText = log[currentRow][targetField];
        const currentLength = newProgress[currentRow][currentField];

        // 1. 当前字段未打完:增加长度
        if (currentLength < targetText.length) {
          newProgress[currentRow][currentField] += 1;
          return newProgress;
        }
        // 2. 当前字段打完,切换到同一下一行的下一个字段
        else if (currentField < 2) {
          setCurrentField(prevField => prevField + 1);
          return newProgress;
        }
        // 3. 当前行所有字段打完,切换到下一行的第一个字段
        else if (currentRow < log.length - 1) {
          setCurrentRow(prevRow => prevRow + 1);
          setCurrentField(0);
          return newProgress;
        }
        // 4. 所有内容打完:清除定时器
        else {
          clearInterval(interval);
          return newProgress;
        }
      });
    }, 1000); // 打字速度:每秒1个字符,可自行调整

    return () => clearInterval(interval);
  }, [log, typingProgress, currentRow, currentField]);

  return (
    <div className={classes.logContainer}>
      {log.map((item, rowIdx) => (
        <div key={rowIdx} className={classes.row}>
          <span className={classes.time}>
            {item.time.slice(0, typingProgress[rowIdx]?.[0] || 0)}
          </span>
          <span className={classes.message}>
            {item.message.slice(0, typingProgress[rowIdx]?.[1] || 0)}
          </span>
          <span className={classes.event}>
            {item.eventid.slice(0, typingProgress[rowIdx]?.[2] || 0)}
          </span>
        </div>
      ))}
    </div>
  );
};

export default TypingLog;

方案优势

  • 逻辑清晰:按固定顺序处理,每一步只关注当前要打字的内容,无需复杂的数组操作
  • 状态简洁:用二维数组直接记录进度,避免原方案中混乱的数组拼接逻辑
  • 自动终止:所有内容完成后自动清除定时器,避免内存泄漏
  • 易于扩展:修改打字速度、调整字段顺序或新增字段都只需少量改动

原问题代码(无法正常运行)

const [text, setText] = useState([]);
const [currentIndex, setCurrentIndex] = useState(0);
const [currentText, setCurrentText] = useState([]);

// Setting Text Content For Typing
useEffect(() => {
    if(log && log.length > 0 && text.length == 0){
        log.map((item, key) => {
            let time = item['time'];
            let message = item['message'];
            let event = item['eventid'];
            setText((prev) => [...prev, time, message, event]);
        });
    }
}, [log, text]);

useEffect(() => {
        if(currentText?.length < text?.length){
            const interval = setInterval(()=> {

                // Setting Current index
                if(currentText?.length !== 0 && currentIndex !== currentText?.length -1) {
                    setCurrentIndex(currentText?.length -1);
                }
                
                // Check if the last index string completed
                if(currentText[currentIndex]?.length !== text[currentIndex]?.length){
                    let temp = currentText;
                    let lastText = temp[currentIndex];
                    if(lastText) lastText = lastText + text[currentIndex].charAt(lastText?.length -1);
                    else lastText = text[currentIndex].charAt(0);
                    temp[currentIndex] = lastText;
                    setCurrentText(temp);
                }
                // If completed open new array element to contain new string
                else{
                    setCurrentText((prev) => [...prev, ['']]);
                }
        }, 1000);
        return () => clearInterval(interval);
        }

}, [currentText, text, currentIndex]);

return (
    <>
       {
          currentText && currentText.length > 0 && 
                    currentText.map((item, key) => {
                            <div key={key} className={classes.row}>
                                <span className={classes.time}>{currentText[key]}</span>
                                <span className={classes.message}>{key % 1 ? currentText[key] : currentText[key+1]}</span>
                                <span className={classes.event}>{key % 2 ? currentText[key] : currentText[key+2]}</span>
                            </div>
                        })
        }
</>
);

原数据示例

[
  {time: '2023-02-19 06:25:30', message: 'some message', eventid: 'event_string'},
  {time: '2023-02-19 06:25:30', message: 'some message', eventid: 'event_string'},
  {time: '2023-02-19 06:25:30', message: 'some message', eventid: 'event_string'},
]

内容的提问来源于stack exchange,提问作者David Sachakov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 10:55:51