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

基于Youtube API的Reactjs无限滚动功能实现难题

React + YouTube API 无限滚动实现方案

核心实现思路

基于YouTube API的pageToken分页机制,结合Intersection Observer API监听列表底部元素,自动触发下一页数据加载,避免传统scroll事件的性能问题。

分步实现代码

1. 状态定义

import { useState, useEffect, useRef } from 'react';

const YoutubeInfiniteScroll = () => {
  // 存储视频列表
  const [videos, setVideos] = useState([]);
  // YouTube API分页token
  const [nextPageToken, setNextPageToken] = useState(null);
  // 加载状态,防止重复请求
  const [isLoading, setIsLoading] = useState(false);
  // 监听用的DOM引用
  const observerTarget = useRef(null);

2. 数据请求函数

const fetchVideos = async () => {
    if (isLoading) return;
    setIsLoading(true);
    
    const API_KEY = '你的YouTube API密钥';
    const SEARCH_QUERY = '你的搜索关键词';
    const MAX_RESULTS = 10;

    try {
      const url = new URL('https://www.googleapis.com/youtube/v3/search');
      url.searchParams.set('part', 'snippet');
      url.searchParams.set('maxResults', MAX_RESULTS);
      url.searchParams.set('q', SEARCH_QUERY);
      url.searchParams.set('key', API_KEY);
      if (nextPageToken) url.searchParams.set('pageToken', nextPageToken);

      const response = await fetch(url.toString());
      const data = await response.json();

      // 追加新视频,而非覆盖
      setVideos(prevVideos => [...prevVideos, ...data.items]);
      setNextPageToken(data.nextPageToken);
    } catch (error) {
      console.error('视频加载失败:', error);
    } finally {
      setIsLoading(false);
    }
  };

3. 初始加载与监听逻辑

// 组件挂载时加载第一页数据
  useEffect(() => {
    fetchVideos();
  }, []);

  // 配置Intersection Observer监听底部元素
  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        const entry = entries[0];
        // 元素进入视口、有下一页token、非加载状态时触发请求
        if (entry.isIntersecting && nextPageToken && !isLoading) {
          fetchVideos();
        }
      },
      { threshold: 0.1 }
    );

    if (observerTarget.current) {
      observer.observe(observerTarget.current);
    }

    // 组件卸载时取消监听
    return () => {
      if (observerTarget.current) {
        observer.unobserve(observerTarget.current);
      }
    };
  }, [nextPageToken, isLoading]);

4. 页面渲染

return (
    <div style={{ maxWidth: '1200px', margin: '0 auto', padding: '20px' }}>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: '20px' }}>
        {videos.map(video => (
          <div key={video.id.videoId} style={{ border: '1px solid #eee', borderRadius: '8px', overflow: 'hidden' }}>
            <img 
              src={video.snippet.thumbnails.medium.url} 
              alt={video.snippet.title}
              style={{ width: '100%', height: '180px', objectFit: 'cover' }}
            />
            <div style={{ padding: '10px' }}>
              <h3 style={{ fontSize: '16px', margin: '0 0 8px' }}>{video.snippet.title}</h3>
              <p style={{ fontSize: '14px', color: '#666', margin: '0' }}>{video.snippet.channelTitle}</p>
            </div>
          </div>
        ))}
      </div>

      {isLoading && (
        <div style={{ textAlign: 'center', padding: '20px', fontSize: '18px' }}>
          加载中...
        </div>
      )}

      {/* 用于监听的底部占位元素 */}
      <div ref={observerTarget} style={{ height: '20px', width: '100%' }}></div>
    </div>
  );
};

export default YoutubeInfiniteScroll;

常见问题排查

  • 重复请求:通过isLoading状态严格控制,确保同一时间只有一个请求在进行
  • API配额耗尽:YouTube API有每日请求限额,测试时可减少maxResults或优化请求逻辑
  • 监听不触发:检查observerTarget是否正确挂载,调整threshold参数(比如设为0.01)提高触发灵敏度
  • 移动端适配:Intersection Observer支持所有现代浏览器,如需兼容旧版浏览器,可引入Intersection Observer的polyfill脚本

内容的提问来源于stack exchange,提问作者Dimitris Kermizidis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 12:05:34