Бесконечная прокрутка должна быть проще

В одной из своих статей я объяснил, как создавать собственные useModal() React Hooks, которые можно подключить к модальным компонентам.

В этой статье я объясню, как создать useScrollInfinite() React Hooks, которые можно использовать с компонентом бесконечной прокрутки. Бесконечная прокрутка - это современный подход к разбиению на страницы, вам не нужно переходить на следующую страницу, чтобы увидеть больше контента.

Он автоматически определяет, находитесь ли вы в конце страницы, и загружает дополнительный контент с помощью Ajax и других методов асинхронной выборки данных.

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

Бесконечная прокрутка обычно отлично работает со списками, такими как текст, изображения, видео, обновления статуса, каналы и данные таблиц.

В этом руководстве я буду использовать REST API под названием Medrum для получения случайных статей. Он собирает статьи со всего Интернета и разбивает их на фрагменты данных.

Компонент создания списка (статьи)

Первое, что нужно сделать, - это создать простой компонент списка. Вы можете настроить приложение React с помощью Create React App.

В этом уроке я буду использовать axios в качестве клиентской библиотеки HTTP. Вы можете установить его, как показано ниже:

yarn add axios

Чтобы узнать, как отправить HTTP-запрос в React, посмотрите видео ниже:

Теперь давайте посмотрим на простой компонент ниже:

Вот что мы сделали выше:

  1. Добавлен useState для хранения и установки наших данных.
  2. Добавлен useEffect, чтобы загружать наши данные только при монтировании компонента.
  3. Проверить, готовы ли данные или нет.
  4. Затем отобразите список статей и отобразите их в <li>.

Вот как это выглядит:

Если вы прокрутите вниз, он не загрузит больше данных, так как он еще не может обнаружить дно, и это наш следующий этап руководства. Посмотрим, как мы сможем это осуществить.

Загрузка дополнительных данных путем обнаружения дна

Теперь давайте посмотрим, как мы можем загрузить больше данных, определив, когда вы прокручивались до нижней части основного текста или определенного контейнера.

Есть много способов сделать это, это зависит от вашего варианта использования и того, как выглядит ваша DOM.

Однако наиболее распространенный способ - проверить, равна ли внутренняя высота объекта Window window и window объекта scrollTop документа offsetHeight.


window.addEventListener("scroll", ()=>{
  if(window.innerHeight + document.documentElement.scrollTop!==document.documentElement.offsetHeight){
  return;
}
else {
  console.log("scrolling down");
}
})

С помощью приведенного выше кода мы сможем узнать, когда пользователь прокручивает страницу до конца.

Затем его можно использовать в нашем useEffect хуке жизненного цикла, как показано ниже:

function isScrolling(){
if(window.innerHeight + document.documentElement.scrollTop!==document.documentElement.offsetHeight){
  return;
}
else {
  console.log("scrolling down");
}}
useEffect(()=>{
window.addEventListener("scroll", isScrolling);
return () => window.removeEventListener("scroll", isScrolling);}, [])

В приведенном выше коде мы зарегистрировали нашу isScrolling функцию для прослушивания события scroll. Итак, теперь, когда пользователь прокручивает, вызывается isScrolling.

Поскольку мы будем получать данные, когда пользователь прокручивает страницу вниз, нам нужно знать, когда данные будут завершены, поэтому у нас есть следующее:

const [isFetching, setIsFetching] = useState(false);
function isScrolling(){
if(window.innerHeight + document.documentElement.scrollTop!==document.documentElement.offsetHeight){
  return;
}
else {
  setIsFetching(true)
}}
useEffect(() => {
    if (isFetching){
       console.log("Fetch data")
    }
  }, [isFetching]);

Теперь нам нужно получить данные из API статьи, о котором я упоминал ранее, и мы будем использовать axios:

const [page, setPage] = useState(1);
const fetchData = () =>{
let url = "https://medrum.herokuapp.com/articles";
axios.get(url).then(res => {
setData(res.data);
});
}
const moreData = () => {
let url = `https://medrum.herokuapp.com/feeds/?source=5718e53e7a84fb1901e05971&page=${page}&sort=popular`;
axios.get(url).then(res => {
setData([...data, ...res.data]);
setPage(page+1)
setIsFetching(false)
});
}

Что мы сделали выше:

  1. Мы добавили useState под названием page - будет увеличиваться и использоваться в качестве нумерации страниц для нашего API статьи.
  2. Мы создали две функции: loadData и moreData.
  3. loadData загружает данные при первой загрузке страницы и запускается один раз.
  4. moreData получает больше данных и запускается, когда пользователь прокручивает страницу вниз.
  5. В moreData мы увеличиваем значение состояния страницы и устанавливаем isFetching на false, чтобы сигнализировать о завершении загрузки данных.

Затем, собрав все это вместе, мы можем написать наш List компонент, как показано ниже:

Мы добавили два useEffect - первый, который загружает наши данные при загрузке браузера и связывает наш isScrolling обратный вызов с прослушивателем событий, а второй слушает isFetching useState, а затем получает больше данных, когда он true.

Создание собственного React Hook

Мы видели, как загружать больше данных, когда пользователь прокручивает страницу вниз, и кажется, что все работает нормально.

Однако мы реализовали его только в нашем компоненте статьи, а не в настраиваемом React Hook, который может быть повторно использован другим компонентом.

Давайте посмотрим, как реализовать это в пользовательском React Hook.

Создайте новый useInfinite.js файл в вашем src каталоге и давайте добавим пользовательскую функцию React Hook, как показано ниже:

const useInfiniteScroll = callback => {
  const [isFetching, setIsFetching] = useState(false);
  useEffect(() => {
     if (!isFetching) return;
     callback();
  }, [isFetching]);
  return [isFetching, setIsFetching];
};
export default useInfiniteScroll;

В нашем useInfiniteScroll пользовательском хуке мы:

  1. Добавлен параметр callback, который будет срабатывать всякий раз, когда пользователь прокручивает страницу вниз.
  2. Добавлено isFetching useState, чтобы сигнализировать, когда данные загружаются или нет.
  3. Добавлен useEffect жизненный цикл для вызова нашего обратного вызова, когда isFetching состояние изменяется и значение равно true.
  4. Затем мы экспортируем и isFetching, и setIsFetching, чтобы они были доступны в функциональных компонентах.

Следующее, что нужно сделать, это зарегистрировать нашу isScrolling функцию в scroll event:

Что мы сделали выше:

  1. Добавлен новый жизненный цикл useEffect - он связывает функцию isScrolling с событием прокрутки.
  2. Мы добавили нашу isScrolling функцию.

Вот и все, наш собственный React Hook готов. Давайте посмотрим, как это можно реализовать с помощью нашего компонента article. Перейдите к компоненту статьи и выполните рефакторинг, как показано ниже:

Выше мы внесли некоторые изменения:

  1. Мы импортировали только что созданный useInfinite Hook.
  2. Мы инициализировали useInfiniteScroll custom Hook и передали ему функцию, срабатывающую, когда пользователь прокручивает страницу вниз.
  3. Теперь у нас есть только один useEffect жизненный цикл, который загружает данные при загрузке один раз.
  4. Мы реорганизовали нашу moreData функцию с выражения функции на объявление функции.

Это все. Наконец, теперь мы можем использовать наш собственный React Hook с любым компонентом для загрузки данных, когда пользователь прокручивает страницу вниз.

Вы можете найти весь код в репозитории GitHub:



Чтобы увидеть живую демонстрацию, вы можете проверить ее здесь: https://react-useinfinitescroll.now.sh/