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

React应用初始渲染后持续调用YouTube API问题排查

React应用持续重复调用YouTube API的问题

问题现象

开发的React应用在初始渲染完成后,仍持续向YouTube API发送大量请求,即使已经实现了localStorage缓存机制,网络面板仍能看到重复请求,严重消耗API配额。问题出在实现获取8个特定YouTube视频的自定义hook useVideos之后。

相关代码

自定义hook useVideos

import { useState, useEffect } from "react";
import youtube from "../apis/youtube";

const useVideos = () => {
  const [videos, setVideos] = useState(() => {
    const cachedVideos = localStorage.getItem("cachedVideos");
    return cachedVideos ? JSON.parse(cachedVideos) : [];
  });

  useEffect(() => {
    if (videos.length === 0) {
      fetchSpecificVideos();
    }
  }, []);

  const fetchSpecificVideos = async () => {
    try {
      const videoIds = [
        "VIDEO_1",
        "VIDEO_2",
        "VIDEO_3",
        "VIDEO_4",
        "VIDEO_5",
        "VIDEO_6",
        "VIDEO_7",
        "VIDEO_8",
      ];
      const videoData = [];

      for (const videoId of videoIds) {
        const response = await youtube.get("/videos", {
          params: {
            id: videoId,
            part: "snippet",
            maxResults: 8,
          },
        });
        if (response.data.items && response.data.items.length > 0) {
          videoData.push(response.data.items[0]);
        }
      }

      setVideos(videoData);
      localStorage.setItem("cachedVideos", JSON.stringify(videoData));
    } catch (error) {
      console.error("Error fetching videos:", error);
    }
  };

  return [videos, fetchSpecificVideos];
};

export default useVideos;

YouTube API配置文件

import axios from "axios";

const KEY = "my-youtube-api-key";

export default axios.create({
  baseURL: "https://www.googleapis.com/youtube/v3",
  params: {
    part: "snippet",
    maxResults: 8,
    key: KEY,
  },
});

App组件代码

import React, { useState, useEffect } from "react";
import Header from "./Header";
import VideoList from "./VideoList";
import VideoDetail from "./VideoDetail";
import useVideos from "../hooks/useVideos";

const App = () => {
  const [selectedVideo, setSelectedVideo] = useState(null);
  const [videos, fetchSpecificVideos] = useVideos();

  useEffect(() => {
    if (videos.length === 0) {
      fetchSpecificVideos();
    }
  }, [videos, fetchSpecificVideos]);

  useEffect(() => {
    if (videos.length > 0) {
      setSelectedVideo(videos[0]);
    }
  }, [videos]);

  return (
    <div className="ui container">
      <div className="ui inverted menu">
        <Header />
      </div>
      <VideoList
        onVideoSelect={setSelectedVideo}
        videos={videos}
        selectedVideo={selectedVideo}
      />
      <VideoDetail video={selectedVideo} />
    </div>
  );
};

export default App;

排查情况

  • 仅在App父组件中使用useVideos,未在其他组件中调用
  • 即使启用localStorage缓存,仍会出现大量重复请求
  • 怀疑需要通过记忆化(memoization)解决

解决方案

问题根源

  1. fetchSpecificVideos函数重复创建:useVideos中每次组件渲染时,fetchSpecificVideos都会被重新定义,导致App组件中依赖该函数的useEffect每次都触发,进而重复调用请求。
  2. 双重触发请求:useVideos内部的useEffect已经在初始化时检查并请求数据,App组件中又添加了相同逻辑的useEffect,导致重复触发。

修复步骤

  1. 记忆化请求函数:用useCallback包裹fetchSpecificVideos,避免每次渲染重新创建函数。
  2. 移除重复请求逻辑:保留useVideos内部的初始化请求逻辑,App组件中无需重复判断调用。
  3. 添加请求锁:防止同一时间多次触发请求,避免并发调用API。
  4. 优化请求方式:将串行请求改为并行,减少总请求时间。

修改后的useVideos代码

import { useState, useEffect, useCallback } from "react";
import youtube from "../apis/youtube";

const useVideos = () => {
  const [videos, setVideos] = useState(() => {
    const cachedVideos = localStorage.getItem("cachedVideos");
    return cachedVideos ? JSON.parse(cachedVideos) : [];
  });
  const [isFetching, setIsFetching] = useState(false);

  const fetchSpecificVideos = useCallback(async () => {
    if (isFetching || videos.length > 0) return;
    setIsFetching(true);
    try {
      const videoIds = [
        "VIDEO_1",
        "VIDEO_2",
        "VIDEO_3",
        "VIDEO_4",
        "VIDEO_5",
        "VIDEO_6",
        "VIDEO_7",
        "VIDEO_8",
      ];

      // 并行请求优化
      const requests = videoIds.map(id => 
        youtube.get("/videos", {
          params: {
            id: id,
            part: "snippet",
            maxResults: 1,
          },
        })
      );
      const responses = await Promise.all(requests);
      
      const videoData = responses.reduce((acc, response) => {
        if (response.data.items?.length) acc.push(response.data.items[0]);
        return acc;
      }, []);

      setVideos(videoData);
      localStorage.setItem("cachedVideos", JSON.stringify(videoData));
    } catch (error) {
      console.error("Error fetching videos:", error);
    } finally {
      setIsFetching(false);
    }
  }, [videos, isFetching]);

  useEffect(() => {
    fetchSpecificVideos();
  }, [fetchSpecificVideos]);

  return [videos, fetchSpecificVideos];
};

export default useVideos;

修改后的App组件代码

import React, { useState, useEffect } from "react";
import Header from "./Header";
import VideoList from "./VideoList";
import VideoDetail from "./VideoDetail";
import useVideos from "../hooks/useVideos";

const App = () => {
  const [selectedVideo, setSelectedVideo] = useState(null);
  const [videos] = useVideos();

  useEffect(() => {
    if (videos.length > 0) {
      setSelectedVideo(videos[0]);
    }
  }, [videos]);

  return (
    <div className="ui container">
      <div className="ui inverted menu">
        <Header />
      </div>
      <VideoList
        onVideoSelect={setSelectedVideo}
        videos={videos}
        selectedVideo={selectedVideo}
      />
      <VideoDetail video={selectedVideo} />
    </div>
  );
};

export default App;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 06:12:38