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

React实现多行打字效果如何设置行延迟等待上一行输入完成

问题根因

现有实现出现多行同时打字的核心原因是:遍历文本数组调用createTypingEffect时,所有行的setTimeout计时是并行启动的,每行的字符延迟都是从0开始按100 * 字符索引计算,没有等待上一行打字流程结束就启动了下一行的定时器,符合setTimeout的事件循环执行逻辑。
另外沙盒环境出现字符重复的问题,是因为原代码在setTimeout回调外部提前读取了arrayCopy副本,拿到的是定时器注册时的旧状态,和后续setState的异步更新结果产生冲突。

实现思路

要实现逐行顺序打字效果,只需要把原来并行的定时器逻辑改成串行执行即可:

  • 把单行打字逻辑封装成Promise,在当前行所有字符打印完成后触发resolve
  • 用async/await按顺序逐行调用打字方法,等上一行Promise resolve之后,再启动下一行的打字流程
  • 所有状态更新统一使用函数式setState读取最新的组件状态,避免闭包缓存旧状态导致的字符异常
修正后完整代码
import React from 'react'
import TerminalCursor from 'icons/TerminalCursor'
import TerminalPrompt from 'icons/TerminalPrompt'
import { v4 as uuidv4 } from 'uuid'

type MyProps = {}
type MyState = {
  introArray: string[]
}
class Terminal extends React.Component<MyProps, MyState> {
  state: MyState = {
    introArray: [],
  }

  /**
   * 单行打字方法
   * @param text 要打印的行文本
   * @param lineIndex 行在数组中的索引
   * @param typeSpeed 单字打印间隔,单位ms
   */
  typeSingleLine = (text: string, lineIndex: number, typeSpeed = 100): Promise<void> => {
    return new Promise((resolve) => {
      // 先初始化当前行为空字符串
      this.setState(
        (state) => ({
          introArray: [...state.introArray.slice(0, lineIndex), '', ...state.introArray.slice(lineIndex + 1)],
        }),
        () => {
          // 逐字追加字符
          for (let charIndex = 0; charIndex < text.length; charIndex++) {
            setTimeout(() => {
              this.setState((state) => {
                const currentLineContent = state.introArray[lineIndex]
                return {
                  introArray: [
                    ...state.introArray.slice(0, lineIndex),
                    currentLineContent + text[charIndex],
                    ...state.introArray.slice(lineIndex + 1),
                  ],
                }
              }, () => {
                // 最后一个字符打印完成后,结束当前Promise
                if (charIndex === text.length - 1) resolve()
              })
            }, typeSpeed * charIndex)
          }
        }
      )
    })
  }

  async componentDidMount() {
    // 后续可替换为从sanity拉取的文本数据
    const starterArray = [
      'The very first line that needs to finish typing before going to the next line',
      'The second line that needs to start being typed after the first line',
    ]

    // 按顺序逐行打印,等待上一行完成后再启动下一行
    for (let lineIndex = 0; lineIndex < starterArray.length; lineIndex++) {
      await this.typeSingleLine(starterArray[lineIndex], lineIndex)
      // 如果需要行与行之间加停顿,可以在这里加延迟,比如 await new Promise(r => setTimeout(r, 300))
    }
  }

  render() {
    return (
      <div className="w-1/2 h-1/2 p-5 flex items-start justify-start bg-clip-padding bg-slate-900 backdrop-filter backdrop-blur-xl bg-opacity-60 border border-gray-900 rounded">
        <div className="flex flex-col">
          {this.state.introArray.map((introLine) => (
            <div className="flex items-center" key={uuidv4()}>
              <TerminalPrompt />
              <p className="text-white">{introLine}</p>
            </div>
          ))}
          {/* 底部常驻输入光标 */}
          <div className="flex">
            <TerminalPrompt />
            <TerminalCursor />
          </div>
        </div>
      </div>
    )
  }
}

export default Terminal
扩展说明
  • 这种串行实现的可维护性更高,如果后续需要调整打字速度、增加行间隔停顿、支持中途打断打字效果,只需要修改typeSingleLine方法或者循环里的逻辑即可,不需要手动计算每行动态延迟
  • 如果需要支持删除、光标跳转动画,也可以在这个Promise封装的基础上扩展能力,不需要重构整体逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 00:47:06