React函数组件中使用useRef无法关闭摄像头问题求助
React函数组件摄像头开启/关闭功能修复方案
原代码核心问题
useEffect未设置依赖数组,组件每次渲染都会重复请求摄像头,且清理函数仅在组件卸载时执行,无法响应主动关闭操作- 未将
cameraOpen状态与摄像头启停逻辑绑定,状态和实际操作完全脱节 - 关闭时未清空
<video>的srcObject,视觉上可能残留最后一帧,造成“未关闭”的错觉
修复后的完整代码
import { useRef, useState, useEffect } from 'react'; export default function CameraComponent() { const videoRef = useRef<HTMLVideoElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null); const [cameraOpen, setCameraOpen] = useState(false); // 单独保存媒体流引用,避免从video元素强制类型转换的风险 const streamRef = useRef<MediaStream | null>(null); useEffect(() => { // 状态为false时直接返回,不执行摄像头请求逻辑 if (!cameraOpen) return; const video = videoRef.current; const constraints: MediaStreamConstraints = { audio: false, video: true }; navigator.mediaDevices.getUserMedia(constraints) .then((stream) => { streamRef.current = stream; if (video) { video.srcObject = stream; // 捕获自动播放失败的异常(部分浏览器限制自动播放) video.play().catch(err => console.error('视频播放失败:', err)); } }) .catch((err) => { console.error('摄像头权限请求失败:', err); // 请求失败后重置状态,避免状态与实际设备状态不一致 setCameraOpen(false); }); // 清理函数:当cameraOpen从true变为false时触发 return () => { if (streamRef.current) { // 停止所有媒体轨道 streamRef.current.getTracks().forEach(track => track.stop()); streamRef.current = null; // 清空video的srcObject,消除残留画面 if (videoRef.current) { videoRef.current.srcObject = null; } } }; }, [cameraOpen]); // 依赖cameraOpen状态,状态变化时重新执行逻辑 return ( <div> <video ref={videoRef} width="640" height="480" /> {/* 切换摄像头状态的按钮 */} <button onClick={() => setCameraOpen(prev => !prev)}> {cameraOpen ? '关闭摄像头' : '打开摄像头'} </button> </div> ); }
关键修复说明
- 绑定状态依赖:将
cameraOpen作为useEffect的依赖项,确保只有当用户切换状态时才触发摄像头的开启/关闭逻辑 - 单独跟踪媒体流:用
streamRef保存获取到的MediaStream,避免从video.srcObject强制类型转换带来的潜在错误 - 完善清理逻辑:关闭摄像头时不仅停止轨道,还清空
video的srcObject,让界面直观反馈关闭状态 - 异常处理增强:捕获视频播放失败的异常,以及请求权限失败后重置状态,避免状态不一致的问题
内容的提问来源于stack exchange,提问作者zhaozk
相关产品推荐
相关产品推荐

