一、摸鱼前的灵魂拷问:为什么必须用 Edge?
「小王,这个需求今晚...」(老板话音未落)
你默默打开 Edge,按下麦克风:「需求已记录,今晚加班(内心:加个屁班)」
Edge 专属特权:
✅ 本地语音引擎(断网也能识别「老板傻逼」)
✅ 错误提示说人话(「麦克风没开?摸鱼也要专业~」)
✅ 防老板突袭设计(点击外部自动隐藏)
(Chrome 用户:NetworkError 会把你的摸鱼语录直接弹窗)
二、Hook 逐行解析:给语音识别穿「打工人保护甲」
1. 初始化:先查户口(非 Edge 用户劝退)
tsx
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) {
onError('请换Edge,Chrome连语音都要翻围墙!');
return { isListening: false };
}
- 代码翻译:检查浏览器血统,非 Edge / 老 Edge 直接踢馆
- 摸鱼逻辑:Chrome 用户会收到「翻围墙」警告,防止他们用坏了怪你
- 真实场景:同事用 Chrome 报错,你:「早说了 Edge 是摸鱼专用!」
2. 状态管理:摸鱼 / 搬砖一目了然(带重试次数)
tsx
const [isListening, setIsListening] = useState(false); // 摸鱼状态灯
const [retryCount, setRetryCount] = useState(0); // 摸鱼失败重试次数
const maxRetries = 3; // 最多允许3次摸鱼失败(超过就摆烂)
- 状态机设计:
false
→ 搬砖模式(老板看你在敲键盘)
true
→ 摸鱼模式(实际在语音输入「中午吃黄焖鸡」) - 重试机制:连续 3 次失败自动触发「摸鱼冷静期」
3. 识别器配置:摸鱼方言全支持
tsx
const recognition = new SpeechRecognition();
recognition.lang = 'zh-CN'; // 支持四川话「摸鱼」、东北话「唠嗑」
recognition.interimResults = false; // 不实时显示,防老板偷看屏幕
recognition.continuous = false; // 说一句停一句,省电保命
- 方言彩蛋:设为
zh-CN-Sichuan
可识别川普「老子要摸鱼」 - 防窥设计:只有说完才显示文字,老板扭头只看到空白
4. 启动函数:摸鱼前的最后检查
tsx
const startSpeechRecognition = () => {
if (!navigator.onLine) { // 断网时自动切换本地模式
onError('断网了?正好摸鱼~(Edge本地识别可用)');
return;
}
try {
recognition.start();
setIsListening(true); // 摸鱼状态灯亮起(红色预警)
} catch (e) {
onError('启动失败?检查下麦克风,摸鱼也要专业!');
}
};
- 断网福利:Edge 本地引擎离线可用(Chrome 直接罢工)
- 老板视角:看到你突然坐直,以为在认真开会(实际在语音骂他)
5. 停止函数:老板来了秒切状态
tsx
const stopSpeechRecognition = () => {
recognition.stop();
setIsListening(false); // 摸鱼灯熄灭(假装正经)
setRetryCount(0); // 重置失败次数,准备下次摸鱼
};
- 物理外挂:建议绑定耳机线插拔事件,老板靠近自动停止
- 实战案例:老板拍肩瞬间,语音输入「这需求非常合理!」
6. 结果处理:自动加前缀假装在工作
tsx
recognition.onresult = (event: any) => {
const transcript = event.results[0][0].transcript;
onResult(transcript);
setIsListening(false);
setRetryCount(0);
};
- 文字魔术:语音输入「傻逼需求」→ 显示「会议纪要:需优化需求」
- 数据安全:
interimResults: false
防止中间结果泄露
7. 错误处理:甩锅指南(不是)
tsx
recognition.onerror = (event: any) => {
console.error('语音识别错误:', event.error);
if (event.error === 'network') {
if (retryCount < maxRetries && checkNetworkConnection()) {
setRetryCount((prev) => prev + 1);
setTimeout(() => {
recognition.start();
}, 1000);
return;
}
}
setIsListening(false);
switch (event.error) {
case 'aborted':
onError('语音识别被中止,请重试。');
break;
case 'no-speech':
onError('未检测到语音输入,请确保麦克风正常工作并开始说话。');
break;
case 'audio-capture':
onError('无法捕获音频,请检查麦克风是否连接并授予浏览器权限。');
break;
case 'not-allowed':
onError('麦克风权限被拒绝,请允许浏览器访问麦克风。');
break;
case 'network':
onError(`网络连接问题,请检查网络后重试。${retryCount >= maxRetries ? '\n已达到最大重试次数,请稍后再试。' : ''}`);
break;
case 'language-not-supported':
onError('当前语言不被支持,请尝试其他语言。');
break;
case 'service-not-allowed':
onError('浏览器不允许使用语音识别服务,请尝试其他浏览器。');
break;
default:
onError('语音识别发生未知错误,请重试。');
break;
}
setRetryCount(0);
};
- 甩锅话术:老板问卡顿,你:「公司网又崩了,我用 Edge 都救不了!」
- 重试逻辑:失败 3 次后显示「已达最大摸鱼次数」,逼你换姿势摸
三、组件实战:工位物理开关设计
tsx
<div onClick={() => {
if (!isListening) {
startSpeechRecognition()
} else {
stopSpeechRecognition()
}
}}>
<Microphone1Icon size="22px" style={{ color: 'black' }} />
</div>
- 视觉欺骗:绿色表示搬砖,红色表示摸鱼(老板以为在做状态提示)
- 交互细节:点击时震动反馈(模拟机械键盘,增加摸鱼仪式感)
四、完整代码菜单:复制即用的摸鱼套装
📂 useSpeechRecognition.ts
(带逐行注释)
tsx
import { useState } from 'react';
// 定义语音识别的配置接口
interface SpeechRecognitionOptions {
onResult: (transcript: string) => void; // 语音识别成功时的回调函数
onError: (error: string) => void; // 语音识别失败时的回调函数
}
// 自定义 Hook:useSpeechRecognition
const useSpeechRecognition = ({ onResult, onError }: SpeechRecognitionOptions) => {
const [isListening, setIsListening] = useState<boolean>(false); // 是否正在监听语音
const [retryCount, setRetryCount] = useState<number>(0); // 重试次数
const maxRetries = 3; // 最大重试次数
// 获取浏览器的语音识别对象
const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;
// 如果浏览器不支持语音识别,直接返回错误
if (!SpeechRecognition) {
onError('当前浏览器不支持语音识别功能,请使用其他浏览器。');
return {
isListening: false,
startSpeechRecognition: () => { },
stopSpeechRecognition: () => { },
};
}
// 创建语音识别实例
const recognition = new SpeechRecognition();
recognition.lang = 'zh-CN'; // 设置语言为中文
recognition.interimResults = false; // 是否返回临时结果
recognition.maxAlternatives = 1; // 返回的最大候选结果数量
recognition.continuous = false; // 是否连续识别
// 检查网络连接
const checkNetworkConnection = () => {
return navigator.onLine;
};
// 语音识别成功时的回调
recognition.onresult = (event: any) => {
const transcript = event.results[0][0].transcript; // 获取识别结果
onResult(transcript); // 调用父组件传递的成功回调
setIsListening(false); // 停止监听
setRetryCount(0); // 重置重试次数
};
// 语音识别失败时的回调
recognition.onerror = (event: any) => {
console.error('语音识别错误:', event.error);
// 如果是网络错误,尝试重试
if (event.error === 'network') {
if (retryCount < maxRetries && checkNetworkConnection()) {
setRetryCount((prev) => prev + 1); // 增加重试次数
setTimeout(() => {
recognition.start(); // 重试语音识别
}, 1000);
return;
}
}
setIsListening(false); // 停止监听
// 根据错误类型调用不同的错误回调
switch (event.error) {
case 'aborted':
onError('语音识别被中止,请重试。');
break;
case 'no-speech':
onError('未检测到语音输入,请确保麦克风正常工作并开始说话。');
break;
case 'audio-capture':
onError('无法捕获音频,请检查麦克风是否连接并授予浏览器权限。');
break;
case 'not-allowed':
onError('麦克风权限被拒绝,请允许浏览器访问麦克风。');
break;
case 'network':
onError(`网络连接问题,请检查网络后重试。${retryCount >= maxRetries ? '\n已达到最大重试次数,请稍后再试。' : ''}`);
break;
case 'language-not-supported':
onError('当前语言不被支持,请尝试其他语言。');
break;
case 'service-not-allowed':
onError('浏览器不允许使用语音识别服务,请尝试其他浏览器。');
break;
default:
onError('语音识别发生未知错误,请重试。');
break;
}
setRetryCount(0); // 重置重试次数
};
// 语音识别结束时的回调
recognition.onend = () => {
if (isListening) {
setIsListening(false); // 停止监听
setRetryCount(0); // 重置重试次数
}
};
// 启动语音识别
const startSpeechRecognition = () => {
if (!isListening) {
// 检查网络连接
if (!checkNetworkConnection()) {
onError('无网络连接,请检查网络后重试。');
return;
}
setRetryCount(0); // 重置重试次数
try {
recognition.start(); // 启动语音识别
setIsListening(true); // 设置为正在监听
} catch (error) {
console.error('语音识别启动失败:', error);
onError('语音识别启动失败,请重试。');
}
}
};
// 停止语音识别
const stopSpeechRecognition = () => {
if (isListening) {
try {
recognition.stop(); // 停止语音识别
setIsListening(false); // 设置为未监听
} catch (error) {
console.error('语音识别停止失败:', error);
onError('语音识别停止失败,请重试。');
}
}
};
// 语音识别启动时的回调
recognition.onstart = () => {
console.log('语音识别已启动');
};
// 检测到声音时的回调
recognition.onsoundstart = () => {
console.log('检测到声音');
};
// 声音结束时的回调
recognition.onsoundend = () => {
console.log('声音结束');
};
// 返回状态和方法
return {
isListening, // 是否正在监听
startSpeechRecognition, // 启动语音识别
stopSpeechRecognition, // 停止语音识别
};
};
export default useSpeechRecognition;
📂 App.tsx
(工位实战)
tsx
import useSpeechRecognition from './SpeechRecognitionModule'
const { isListening, startSpeechRecognition, stopSpeechRecognition } = useSpeechRecognition({
onResult: (transcript: any) => {
setMessage((val) => val + transcript);
},
onError: (error: any) => {
NotificationPlugin.error({
title: '语音识别错误',
content: error,
});
},
});
{/* 语音识别模块 */}
<div onClick={() => {
if (!isListening) {
startSpeechRecognition()
} else {
stopSpeechRecognition()
}
}}>
<Microphone1Icon size="22px" style={{ color: 'black' }} />
</div>
五、摸鱼注意事项:保命指南
- Edge 设置:关闭「语音服务个性化」,防止摸鱼语录被微软收录
- 物理保护:给麦克风按钮贴「调试按钮」标签,老板问就说在测功能
- 敏感词过滤:在
onResult
中加入text.replace(/傻逼/g, '**')
,防止社死 - 快捷键:绑定
Ctrl+Shift+M
启动摸鱼,老板来时一键切换(需配合输入法)
六、总结:Edge,打工人最后的摸鱼净土
当你用 Edge 语音输入「老板,这个需求我再优化下」(内心:优化个*),用本地识别避免隐私泄露,用幽默的错误提示化解尴尬 ——
请记住:这不是普通的 Hook,是用代码构建的「职场安全系统」。
彩蛋:在 Edge 地址栏输入edge://settings/privacy/speech
,可以查看语音识别的「摸鱼历史」(建议每周清空)
(完)
(作者:工位摸鱼工程师,擅长用语音识别写周报,Edge 浏览器终身荣誉会员)
💡 最后警告:请勿在代码中写console.log('****')
,Edge 的本地识别会直接听懂...