React自定义视频播放Hook使用useRef在iOS Safari返回时报TypeError
问题原因
- 页面点击返回触发组件卸载时,React会先把绑定的ref置为
null,而iOS Safari的IntersectionObserver在页面销毁的过程中仍可能触发回调,此时访问videoRef.current就会为空导致报错。 - 现有代码缺少对ref存在性的防御判断,且
IntersectionObserver的清理逻辑不够彻底。 - 你的
useEffect依赖数组写法存在小问题:wrapperRef本身是稳定的ref对象,不需要作为依赖,而你调用Hook时传入的options是字面量对象,每次组件渲染都会生成新实例,会导致useEffect重复执行。
修复步骤
1. 调整自定义Hook逻辑
- 给回调函数增加ref存在性判断
- 清理函数中追加
observer.disconnect()彻底销毁监听 - 修正useEffect依赖项
- 给play方法加catch捕获潜在异常(iOS Safari对视频播放的限制较多,避免未捕获错误)
修复后的usePlayVideo代码:
const usePlayVideo = options => { const wrapperRef = useRef(null); const videoRef = useRef(null); const handleVid = entries => { const [entry] = entries; // 先判断ref是否存在,为空直接终止逻辑 if (!videoRef.current) return; if (entry.isIntersecting) { // play返回Promise,加catch避免播放限制导致的未捕获错误 videoRef.current.play().catch(() => {}); } else { videoRef.current.pause(); } }; useEffect(() => { const wrapper = wrapperRef.current; if (!wrapper) return; const observer = new IntersectionObserver(handleVid, options); observer.observe(wrapper); return () => { observer.unobserve(wrapper); // 彻底销毁观察者,避免组件卸载后触发回调 observer.disconnect(); }; // ref对象本身稳定不需要作为依赖,仅保留options即可 }, [options]); return [wrapperRef, videoRef]; }; export { usePlayVideo };
2. 优化调用侧的options(可选但推荐)
因为你调用时传入的options是字面量,每次渲染都会生成新对象,导致useEffect重复执行,建议用useMemo包一下:
function ClientAsk() { // 缓存options,避免重复触发useEffect const observerOptions = useMemo(() => ({ root: null, rootMargin: "0px", threshold: 0.3, }), []); const [wrapperRef, videoRef] = usePlayVideo(observerOptions); return ( {/* bunch of other divs and stuff */} <div className="grid_s_start_3 grid_s_span_20" ref={wrapperRef}> <video ref={videoRef} id="video" playsInline muted preload="auto" loop style={{ width: "100%" }}> <source src={ClientVid} type="video/mp4" /> </video> </div> {/* rest of other divs and stuff */} ); } export default ClientAsk;
补充说明
这个问题确实是iOS Safari独有的事件触发时机差异导致的,不是useRef默认值设为null的问题,加防御判断和彻底清理观察者即可解决。
内容的提问来源于stack exchange,提问作者Holy_Gr33n
相关产品推荐
相关产品推荐

