You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 00:53:27