语音输入模块
## 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/voice的Voice是全局单例 - 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上添加styleprop 处理外层间距 - 长远方案:统一 UI 组件样式规范
采纳方案:短期方案
3. 小结
语音输入模块的核心挑战是 @react-native-voice/voice 的全局单例特性。通过采用全局单例引擎 + pageId 路由的架构模式,彻底解决了多页面冲突问题。UI 层面通过移除 Fragment 并添加独立的 style prop,确保 flex 布局正确生效。整体设计简洁可靠,便于后续维护和扩展。