前端模拟 流式文本接口 打字机效果 mockStreamText

522 阅读4分钟

源码已在文章最后给出

说明书

mockStreamText 这是一个用于在前端 模拟文本流式 展示效果的工具函数,能够将完整文本按指定的片段大小和时间间隔逐步展示,适用于 模拟 AI 对话回复打字机效果 等场景。函数具备完善的参数验证和错误处理机制,确保运行的健壮性。

参数说明

参数名类型必选说明
fullTextstring需要进行流式展示的完整文本内容
optionsobject配置选项对象,包含以下属性

options 配置项

配置项类型默认值说明
chunkSizenumber5每次展示的文本片段大小(正整数)
intervalnumber50每次展示的时间间隔(毫秒,正整数)
onChunkfunction() => {}每次片段更新时的回调函数,接收当前已展示的文本作为参数
onFinalfunction() => {}文本全部展示完成时的回调函数,接收完整文本作为参数
onErrorfunction(err) => console.error(...)发生错误时的回调函数,接收错误对象作为参数

返回值

返回一个取消函数,调用该函数可以终止流式展示过程并释放相关资源。

错误处理

函数会对输入参数进行严格验证,当参数不符合要求时会通过 onError 回调抛出以下错误:

  • fullText 必须是字符串类型:当第一个参数不是字符串时
  • chunkSize 必须是正整数:当 chunkSize 不是正整数时
  • interval 必须是正整数:当 interval 不是正整数时
  • onChunk, onFinal 和 onError 必须是函数:当回调函数不是函数类型时

使用示例

以下仅展示核心的JS食用方法

(利用vue的响应式,displayText内容变化,会自动触发页面渲染,实现打字机效果)

// 引入前端虚拟流式加载工具类
import { mockStreamText } from "@/utils/typeWriter.js"

const backendText = "这是一段需要流式展示的文本内容...";
const displayText = ref("");
const isStreaming = ref(true);

// 启动流式展示
const cancelStream = mockStreamText(backendText, {
    chunkSize: 5,          // 每次展示5个字符
    interval: 50,          // 每50毫秒展示一次
    onChunk: (text) => {   // 片段更新时更新显示
        displayText.value = text;
    },
    onFinal: (fullText) => {  // 展示完成时
        isStreaming.value = false;
        console.log('流式展示结束');
    },
    onError: (err) => {    // 错误处理
        isStreaming.value = false;
        console.error('发生错误:', err.message);
        // 可以在这里添加错误提示UI
    }
});

// 如需中途取消展示
// cancelStream();

特殊情况处理

  • 当 fullText 为空字符串时,会直接触发 onChunk("") 和 onFinal(""),不启动定时器
  • 调用返回的取消函数后,会终止定时器并标记为已取消,避免后续回调执行

工作原理

  1. 进行参数验证,确保输入符合要求
  2. 初始化状态变量(当前位置、定时器、取消标记等)
  3. 定义核心处理函数 process,负责计算片段、触发回调和状态更新
  4. 立即执行一次 process 并设置定时器循环执行
  5. 返回取消函数,用于终止流程并释放资源

该工具函数源码


// 模拟文本流式展示的工具函数(增强健壮性版本)
export function mockStreamText(fullText, options = {}) {
  // 1. 参数验证与默认值处理
  if (typeof fullText !== "string") {
    throw new TypeError("fullText 必须是字符串类型")
  }

  // 默认配置
  const {
    chunkSize = 5,
    interval = 50,
    onChunk = () => {},
    onFinal = () => {},
    onError = (err) => console.error("流式处理错误:", err),
  } = options

  // 验证配置参数的有效性
  if (!Number.isInteger(chunkSize) || chunkSize <= 0) {
    onError(new Error("chunkSize 必须是正整数"))
    return () => {} // 返回空的取消函数
  }

  if (!Number.isInteger(interval) || interval <= 0) {
    onError(new Error("interval 必须是正整数"))
    return () => {}
  }

  if (
    typeof onChunk !== "function" ||
    typeof onFinal !== "function" ||
    typeof onError !== "function"
  ) {
    onError(new Error("onChunk, onFinal 和 onError 必须是函数"))
    return () => {}
  }

  // 2. 状态变量初始化
  let currentPosition = 0
  let timer = null
  let isCanceled = false
  const textLength = fullText.length

  // 3. 核心处理函数(带错误捕获)
  const process = () => {
    if (isCanceled) return

    try {
      // 计算下一次要展示到的位置
      const nextPosition = Math.min(currentPosition + chunkSize, textLength)

      // 获取当前片段并更新位置
      const currentChunk = fullText.substring(0, nextPosition)
      currentPosition = nextPosition

      // 触发片段回调
      onChunk(currentChunk)

      // 检查是否已经完成所有文本的展示
      if (currentPosition >= textLength) {
        clearInterval(timer)
        timer = null
        onFinal(currentChunk) // 触发结束回调
      }
    } catch (err) {
      // 错误处理
      clearInterval(timer)
      timer = null
      onError(err)
    }
  }

  // 4. 启动流式处理(空文本特殊处理)
  if (textLength === 0) {
    // 空文本直接触发结束回调
    onChunk("")
    onFinal("")
    return () => {}
  }

  // 立即执行一次,然后设置定时器
  process()
  timer = setInterval(process, interval)

  // 5. 返回取消函数(确保资源正确释放)
  return () => {
    if (!isCanceled) {
      isCanceled = true
      if (timer) {
        clearInterval(timer)
        timer = null
      }
    }
  }
}