ReactJs useRef current返回null 组件卸载调用stopCamera报错如何解决
问题根因
- 组件卸载执行清理逻辑时,
<video>DOM 元素已经被React从页面移除,videoElement.current变为null,原有stopCamera直接访问videoElement.current.srcObject会触发空引用报错。 navigator.mediaDevices.getUserMedia是异步接口,可能存在组件已经卸载,但摄像头授权/流返回的回调才执行的情况,此时访问已销毁的ref对象也会引发异常,还可能导致摄像头、手电筒资源泄漏无法正常关闭。- 媒体流仅绑定在video元素的
srcObject属性上,DOM销毁后就无法获取流实例,无法保证所有媒体轨道被正确停止。
修复方案
修改CameraScreen.js代码:
import "./styles.css"; import { useState, useEffect, useRef } from "react"; export default function CameraScreen() { const videoElement = useRef(null); // 新增:单独存储媒体流实例,不依赖DOM元素 const mediaStreamRef = useRef(null); // 新增:标记组件是否处于挂载状态 const isMountedRef = useRef(true); const [facingMode, setFacingMode] = useState("environment"); const handleFacingModeToggle = () => { stopCamera(); setFacingMode(prev => prev === "environment" ? "user" : "environment"); }; useEffect(() => { startCamera(); return function cleanup() { isMountedRef.current = false; stopCamera(); }; }, [facingMode]); // 注意:facingMode变更需要重新启动相机,所以要加入依赖数组 const stopCamera = () => { // 先停止所有媒体轨道 if (mediaStreamRef.current) { mediaStreamRef.current.getTracks().forEach(t => t.stop()); mediaStreamRef.current = null; } // 再清理video元素的srcObject,增加判空避免报错 if (videoElement.current) { videoElement.current.srcObject = null; } }; function startCamera() { if (!navigator.mediaDevices?.getUserMedia) return; navigator.mediaDevices .getUserMedia({ video: { facingMode: facingMode }, width: { ideal: 1280 }, height: { ideal: 720 } }) .then(function (stream) { // 组件已卸载的情况下直接停止流,不执行后续逻辑 if (!isMountedRef.current) { stream.getTracks().forEach(t => t.stop()); return; } // 存储流实例 mediaStreamRef.current = stream; if (videoElement.current) { videoElement.current.srcObject = stream; } const track = stream.getVideoTracks()[0]; const imageCapture = new ImageCapture(track); return imageCapture.getPhotoCapabilities(); }) .then(() => { if (!isMountedRef.current || !mediaStreamRef.current) return; const track = mediaStreamRef.current.getVideoTracks()[0]; track.applyConstraints({ advanced: [{ torch: true }] }); }) .catch(function (error) { if (!isMountedRef.current) return; alert("请检查设备权限"); console.log("调用摄像头出错:", error); }); if (videoElement.current) { videoElement.current.onloadeddata = function () { if (window.NativeDevice) { window.NativeDevice.htmlCameraReadyToRecord(true); } }; } } return ( <> <video autoPlay ref={videoElement} style={{ minHeight: "67.82vh", maxHeight: "67.82vh", maxWidth: "100%", minWidth: "100%" }} className="border-3rem bg-[#666]" ></video> <button onClick={stopCamera}>停止摄像头</button> </> ); }
核心修改说明:
- 新增
mediaStreamRef单独存储媒体流实例,停止摄像头时优先从该ref获取流,不依赖DOM元素的存在,保证资源可以正常释放。 - 新增
isMountedRef标记组件挂载状态,所有异步回调执行前先判断组件是否已卸载,避免访问已销毁的对象。 - 优化
stopCamera逻辑,增加多层判空防护,避免空引用报错。 - 修正
useEffect依赖数组,加入facingMode保证切换前后摄像头时可以正确重启相机。
内容的提问来源于stack exchange,提问作者TINTIN
相关产品推荐
相关产品推荐

