Почему мое приложение React постоянно отправляет запросы к API YouTube после первоначального рендеринга?

Я изо всех сил пытаюсь понять, что именно происходит с API Youtube.

Кажется, приложение, которое я разработал в React, постоянно отправляет запросы к API Youtube даже после завершения первоначального рендеринга. Мне пришлось вернуться и освежить свое понимание useEffect, и я понял, что он настроен правильно, а затем, даже после того, как я реализовал способ кэширования первоначальных запросов, похоже, он все еще отправляет запросы, судя по тому, что я могу сказать на вкладке сети, и очевидно, это съедает мою квоту.

Я начал замечать это после того, как разработал собственный крючок для хранения восьми очень специфических видео с YouTube. Это мой 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;

ПРИМЕЧАНИЕ. Я заменил фактические идентификаторы на те, которые вы видите выше.

В моем фактическом файле 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,
  },
});

Я добавляю скриншот вкладки сети, возможно, я неправильно понимаю, что происходит, но для меня это выглядит как множество запросов всего на 8 очень конкретных видео, которые загружаются один раз при рендеринге приложения. Это происходит с реализацией localStorage или без нее:

Мне интересно, относится ли это к какому-то типу запоминания.

Коллега предположил, что если несколько (N) компонентов используют специальный крючок useVideo, то все эти компоненты инициируют асинхронный запрос, что имеет смысл, однако только родительский компонент приложения использует этот пользовательский крючок. Я размещаю здесь компонент App.js:

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;

Пользовательский хук не используется ни в каких других компонентах.

Вопрос: происходит ли описанное вами поведение при начальной загрузке приложения, когда данные локального хранилища пусты, или даже происходит после этого? Первоначально, когда localstorage пуст и videos есть [] - fetchSpecificVideos произойдет столько раз, сколько вы использовали этот хук (для видимых/отображаемых компонентов). Чтобы сделать это лучше, вам нужно добавить контекст, который будет содержать все данные и функции, обернуть приложение этим контекстом и переписать этот крючок, чтобы он полагался на значения и функции в контексте, таким образом, он будет действовать близко к синглтону. .

Sergey Sosunov 15.06.2024 17:56

@СергейСосунов, это происходило без всякого localStorage участия. Я добавил localStorage для кэширования выбранных конкретных видео, чтобы они перестали загружаться, но, похоже, это не работает. Поможет ли это прояснить, что происходит? Заранее спасибо.

Daniel 15.06.2024 19:12

У вас есть «только монтаж», при котором эффект работает только при первоначальном рендеринге. Что в вашем коде возможно перемонтируется для повторного запуска вашего пользовательского useVideos хука? Пожалуйста, отредактируйте , включив в него полный минимально воспроизводимый пример соответствующего кода, с которым вы работаете.

Drew Reese 15.06.2024 19:46

@Daniel Я хотел сказать, что если ваше локальное хранилище пусто при первом посещении пользователя и несколько (N) компонентов используют ваш крючок, то все эти компоненты будут инициализировать (N * videos_count) асинхронные запросы к API YouTube, и только после этой асинхронности операция завершена - они ВСЕ запишут некоторые данные в локальное хранилище и только потом эти данные «кэшируются». Вот почему я предлагаю добавить контекст верхнего уровня для хранения данных и загрузки этих данных, чтобы у вас был только один верхний компонент, который отвечает за загрузку и сохранение данных. А затем просто используйте эти данные через перехватчик или useContext.

Sergey Sosunov 15.06.2024 20:07

@СергейСосунов, ааа, спасибо за разъяснения. Теперь я понимаю, но, Сергей, чтобы внести ясность, только компонент приложения использует специальный крючок useVideo, поможет ли мне поделиться этим файлом?

Daniel 15.06.2024 21:37
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
4
5
60
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Первая проблема: fetchSpecificVideos не объявлен с useCallback хуком и не используется в useEffect dependency array компоненте App. По этой причине - каждый раз, когда компонент приложения перерисовывается из-за какого-либо изменения состояния - функция fetchSpecificVideos воссоздается с новым reference и это заставляет useEffect повторно выполняться, и если повторная отрисовка и повторное выполнение происходят из-за какого-либо другого несвязанного изменения состояния. и до заполнения videos будут выполнены дополнительные вызовы API YouTube.

Если вам действительно нужно использовать этот метод вне хука — оберните fetchSpecificVideos с помощью useCallback или не помещайте его в массив зависимостей useEffect/useCallback:

const fetchSpecificVideos = useCallback(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);
  }
}, []);

Следующая и основная проблема: у вас есть useEffect для вызова fetchSpecificVideos if videos.length === 0 как внутри хука, так и внутри компонента приложения, который... избыточен и съедает вашу фактическую квоту. Было бы лучше, если бы крючок useVideos мог вызывать только этот метод, не экспортируйте fetchSpecificVideos из него, а просто вызывайте его внутри.

Также я продублирую свой комментарий здесь для будущих читателей: если localstorage пуст, при первом посещении пользователя и несколько (N) компонентов используют ваш хук, тогда все эти компоненты будут инициализировать (N * videos_count) асинхронные запросы к API YouTube, и только после выполнения этой асинхронной операции - все они начнет записывать полученные данные в локальное хранилище, и только затем эти данные «кэшируются». Кроме того, все они будут вызывать метод setVideos с одинаковым, но разным массивом ссылок, что может повлиять на другие useEffects. Предлагается добавить контекст верхнего уровня, который будет загружать и сохранять данные, а затем просто использовать эти данные через перехватчик или useContext.

Другие вопросы по теме