【翻译】使用 React 的 <Activity /> 组件预加载视频

31 阅读2分钟

原文链接:www.epicreact.dev/use-react-a…

image.png

作者:Kent C. Dodds

您是否知道,当视频元素采用条件渲染时,其预加载属性 preload="auto" 将失效?React 的新组件 <Activity> 巧妙解决了这个问题。具体方法如下:

问题

假设你有一个包含预告片的电影目录。当用户点击"观看预告片"时,你希望播放视频,并希望预先加载视频以提升用户体验:

{
	showTrailer ? <video src={movie.videoUrl} preload="auto" controls /> : null
}

问题在于:如果视频元素不在DOM中,浏览器就无法预加载它。因此当用户在网速较慢的环境下点击"观看预告片"时,仍需等待视频加载完成,这完全违背了预加载的初衷。

你可能会想:"那我直接用 display: none 之类的样式代替条件渲染不就行了!"这种方法或许可行,但其实还有更好的解决方案。

解决方案:Activity

React 19 的 <Activity> 组件正是为此而生。无需条件渲染视频,只需将其包裹在 Activity 边界中:

<Activity mode={showTrailer ? 'visible' : 'hidden'}>
    <div className="overflow-hidden rounded-lg">
        <video
            src={movie.videoUrl}
            title={movie.title}
            loop
            controls
            preload="auto"
        />
    </div>
</Activity>

现在视频元素始终存在于DOM中,因此 preload="auto" 确实有效!当用户在网速较慢时点击"观看预告片",视频会立即显示,因为它已在后台完成加载。

完整组件如下:

export function MovieTrailer({ movie }: { movie: Movie }) {
    const [showTrailer, setShowTrailer] = useState(false)
    useAutoplay(showTrailer)

    return (
        <div className="mb-6">
            <button
                onClick={() => setShowTrailer(!showTrailer)}
                className="rr-button mb-4"
            >
                {showTrailer ? 'Hide Trailer' : 'Watch Trailer'}
            </button>
            <Activity mode={showTrailer ? 'visible' : 'hidden'}>
                <div className="overflow-hidden rounded-lg">
                    <video
                        src={movie.videoUrl}
                        title={movie.title}
                        loop
                        controls
                        preload="auto"
                    />
                </div>
            </Activity>
        </div>
    )
}

为什么是Activity?

Activity状态不仅能通过 display: none 实现隐藏/显示。当隐藏时,它会:

  • 视觉上隐藏子元素
  • 清理效果(暂停订阅等操作)
  • 保留组件状态以便再次显示时恢复
  • 允许 DOM 继续存在(因此预加载功能正常工作!)

这使其成为需要用户再次交互的内容的理想选择,例如标签页、模态框,或像我们案例中需要预加载的视频。

处理视频字段播放

由于使用Activity时视频始终存在于DOM中,因此需谨慎处理自动播放功能。以下是一个自定义钩子,用于在视频隐藏时暂停播放:

function useAutoplay(showTrailer: boolean) {
    useEffect(() => {
        const video = document.querySelector('video')
        if (!(video instanceof HTMLVideoElement)) return
        if (!showTrailer) {
            void video.pause()
            return
        }

        video.volume = 1
        void video.play()
        return () => {
            void video.pause()
        }
    }, [showTrailer])
}

这确保视频在隐藏时暂停,在显示时播放,让您两全其美:既能预加载内容,又能实现精准的播放控制。

了解更多

Activity 是 React 19 中一个强大的新组件,其应用场景远不止视频预加载。查阅 React 文档了解:

  • 隐藏组件时的状态恢复
  • DOM 状态的保留(如表单输入)
  • 提升 hydration 性能
  • 以及更多功能!

Activity 完美展现了 React 解决实际问题的实力。快在你的下个项目中尝试吧!