React中setState致<video>播放重渲染重启,memo/useRef无效求助
问题分析
你的Player组件虽使用了React.memo,但播放时仍会触发重渲染导致视频重启,核心原因有两个:
- 传给
PlayerMemo的onTimeUpdate是每次VideoEdit渲染时新建的箭头函数,memo会判定props发生变化,触发组件重渲染; Player中src={URL.createObjectURL(file)}每次渲染都会生成新的Blob URL,直接导致<video>元素重新加载视频。
解决方案
1. 用useCallback稳定onTimeUpdate引用
在VideoEdit组件中,通过useCallback包裹时间更新函数,确保其引用在组件生命周期内保持稳定:
const onTimeUpdate = useCallback(() => { const time = videoEl.current?.currentTime; if (!time) return; setPlayTime(time); }, []); // videoEl是ref类型,无需加入依赖数组
2. 缓存Blob URL避免重复生成
在Player组件中,用useMemo缓存URL.createObjectURL(file)的结果,仅当file变化时重新生成,同时加入清理逻辑避免内存泄漏:
function Player({ videlEl, onTimeUpdate = () => {}, }: { videlEl: React.RefObject<HTMLVideoElement>; onTimeUpdate: () => void; }) { const { file } = useVideoCreateStore(); const videoSrc = useMemo(() => { if (!file) return ''; const url = URL.createObjectURL(file); // 组件卸载或file更新时释放旧URL return () => { URL.revokeObjectURL(url); }, url; }, [file]); if (!file) return null; return ( <video ref={videlEl} src={videoSrc} controls onTimeUpdate={onTimeUpdate} /> ); }
3. 确保memo正确生效
修改后的Player组件通过memo包裹后,仅当videlEl、onTimeUpdate或file发生变化时才会重渲染,彻底避免播放过程中的不必要更新。
完整修改代码示例
import { memo, useCallback, useMemo, useRef, useState } from 'react'; function Player({ videlEl, onTimeUpdate = () => {}, }: { videlEl: React.RefObject<HTMLVideoElement>; onTimeUpdate: () => void; }) { const { file } = useVideoCreateStore(); const videoSrc = useMemo(() => { if (!file) return ''; const url = URL.createObjectURL(file); return () => { URL.revokeObjectURL(url); }, url; }, [file]); if (!file) return null; return ( <video ref={videlEl} src={videoSrc} controls onTimeUpdate={onTimeUpdate} /> ); } const PlayerMemo = memo(Player); export default function VideoEdit({ video }: VideoEditProps) { const [playTime, setPlayTime] = useState(0); const videoEl = useRef<HTMLVideoElement>(null); const onTimeUpdate = useCallback(() => { const time = videoEl.current?.currentTime; if (!time) return; setPlayTime(time); }, []); return ( <> <div className="relative border rounded-sm"> <PlayerMemo videlEl={videoEl} onTimeUpdate={onTimeUpdate} /> </div> <Button onClick={() => { capture2(); }} > Capture </Button> </> ); }
内容的提问来源于stack exchange,提问作者PGT
相关产品推荐
相关产品推荐

