语音输入模块

## 1. 问题背景

by 于成季 ·
知与行 CloudBase

1. 问题背景

知与行 App 的新建计划、新建感悟、神秘人聊天三个场景都需要文字输入,依赖键盘打字体验割裂。目标实现通用语音输入模块。

2. 问题列表

2.1 语音识别功能完全无法使用

根因

  • why1: 为什么 createVoiceInputEngine is not a function,因为 Metro bundler 无法正确解析 re-export 的函数
  • why2: 为什么无法解析,因为 index.ts 使用 export { createVoiceInputEngine } from './VoiceInputEngine' 写法在某些情况下模块解析失败

方案

  • 短期方案:直接在 index.ts 中定义 createVoiceInputEngine 函数
  • 长远方案:使用统一的模块导出规范

采纳方案:短期方案

2.2 多页面语音输入互相干扰

根因

  • why1: 为什么第二个页面语音识别无反应,因为 @react-native-voice/voiceVoice 是全局单例
  • why2: 为什么全局单例会冲突,因为旧引擎的监听器会覆盖新引擎的监听器
  • why3: 为什么旧监听器还在,因为 stop() 没有清除监听器,事件仍在队列中

方案

  • 短期方案:用 startId 标记会话,每次 stop() 递增使旧监听器忽略新事件
  • 长远方案:使用全局单例引擎,通过 pageId 区分当前活跃页面

采纳方案:长远方案

2.3 语音识别结果重复累加

根因

  • why1: 为什么文字会重复,因为 onSpeechResults 每次返回从开始到当前的全部文本("测"→"测试"→"测试内"→"测试内容")
  • why2: 为什么需要处理增量,因为 iOS 语音识别会持续返回累积结果

方案

  • 短期方案:用 lastProcessedLengthRef 跟踪已处理长度,只追加新内容
  • 长远方案:同短期方案

采纳方案:短期方案

2.4 新建计划页面按钮样式不正确

根因

  • why1: 为什么按钮宽度不对,因为 <> React Fragment 不会建立 flex 上下文,导致 flex: 1 无法生效
  • why2: 为什么 flex 不生效,因为 Fragment 只是一个逻辑包装,不参与布局计算

方案

  • 短期方案:移除 Fragment,用 View 替代,并在 VoiceInputButton 上添加 style prop 处理外层间距
  • 长远方案:统一 UI 组件样式规范

采纳方案:短期方案

3. 小结

语音输入模块的核心挑战是 @react-native-voice/voice 的全局单例特性。通过采用全局单例引擎 + pageId 路由的架构模式,彻底解决了多页面冲突问题。UI 层面通过移除 Fragment 并添加独立的 style prop,确保 flex 布局正确生效。整体设计简洁可靠,便于后续维护和扩展。

← 所有文章