Пример бесконечной прокрутки React с TypeScript и NextJS

Для непосвященных бесконечная прокрутка (иначе известная как бесконечная прокрутка) означает метод автоматической загрузки данных, когда пользователь прокручивает экран до нижней границы, что позволяет ему продолжить просмотр контента с минимальными усилиями. Если вы работаете в сфере front-end со старых добрых времен, когда jQuery был передовым, это, вероятно, звучит как раздражающая функция, которую нужно создавать. Однако с современными API браузера и библиотеками JavaScript, такими как React, создание такого эффекта стало на порядки менее болезненным.

В этой статье мы рассмотрим несколько библиотек, которые могут облегчить бесконечную прокрутку в React. Если это не ваш стиль, мы также рассмотрим пример бесконечной прокрутки, использующий Intersection Observer API и NextJS, а также затронем тактику бесконечной прокрутки SSR (рендеринг на стороне сервера), которая может помочь в поисковой оптимизации.

Зачем внедрять бесконечную прокрутку?

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

Если у вас большой объем данных, вам необходимо разделить их на части, чтобы страница загружалась быстрее. Именно здесь на помощь приходят стратегии пагинации, такие как бесконечная прокрутка. Пагинация — это практика получения данных небольшими фрагментами (или страницами), вместо того чтобы получать все данные сразу.

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

Библиотеки для облегчения работы с React Infinite Scroll

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

  • react-waypoint: Если говорить о готовых решениях, то это мой личный фаворит. Библиотека проста в использовании, и вы можете реализовать бесконечную прокрутку, используя лишь компонент Waypoint и обработчик onEnter. Имея 4 000 звезд на GitHub по состоянию на август 2022 года, react-waypoint является далеко не самой популярной из перечисленных здесь библиотек, хотя она также является более универсальной библиотекой, чем ее аналоги.
  • react-infinite-scroll-hook: Это еще одна библиотека, которая выделяется из толпы. Я лично не использовал ее, но, похоже, она поддерживается более активно, чем react-infinite-scroll-component. Вдобавок ко всему, она имеет мизерный размер минифицированного пакета в 1,8 кБ, согласно bundlephobia, что привлекает во мне минималиста.
  • react-infinite-scroll-component: Я нашел этот, казалось бы, популярный компонент в ходе своего исследования для этой статьи. Я не использовал его сам и не могу поручиться за него, но он постоянно всплывал, поэтому я решил, что должен добавить его в этот список. Стоит отметить, что по состоянию на август 2022 года эта библиотека имеет 114 открытых проблем на GitHub; по сравнению с react-infinite-scroll-hook (2 открытых проблемы) и react-waypoint (54 открытых проблемы), это довольно большое количество проблем и может быть поводом для беспокойства.

Пользовательская бесконечная прокрутка с помощью TypeScript и NextJS

Для тех, кому требуется (или хочется) индивидуальное решение, не бойтесь: сделать собственную функцию бесконечной прокрутки на удивление просто.

Чтобы помочь вам начать, мы проведем вас через пример приложения, в котором используются React, TypeScript и NextJS. Если вы не знакомы с NextJS, то можете считать, что это специализированный фреймворк, построенный поверх самого React. Не волнуйтесь — пользовательское решение для бесконечной прокрутки React практически идентично пользовательскому решению для бесконечной прокрутки NextJS. Основной причиной, по которой я выбрал NextJS для этого примера, было желание показать взаимодействие между рендерингом на стороне сервера, бесконечной прокруткой и поисковой оптимизацией.

Построение сцены для нашего примера с бесконечной прокруткой

Наш пример — это блог. Когда пользователь переходит на главную страницу нашего приложения, ему показывается набор записей блога; можно считать, что это «страница 1» блога. Когда пользователь доходит до нижней части главной страницы, через вызов API загружаются дополнительные записи блога. Это выглядит примерно так:

Загрузка исходных данных

В этом примере мы будем использовать метод getServerSideProps из NextJS для получения исходных данных, а затем добавим эти исходные данные постов в наш компонент Home. После загрузки первой страницы мы сделаем дополнительные вызовы API и добавим полученные данные в массив dynamicPosts.

// index.tsx (your home page)
export interface BlogPost {
 id: string;
 title: string;
 description: string;
}

export interface HomeProps {
 posts: BlogPost[];
}


export async function getServerSideProps() {
 const posts = [
   {
     id: uuidv4(), // creates a unique ID for the post
     title: "Blog post 1",
     description:
       "Lorem ipsum dolor sit amet consectetur adipisicing elit. Possimus quae numquam repudiandae ab asperiores exercitationem nulla, enim debitis necessitatibus quaerat incidunt nesciunt. Soluta sapiente quisquam magni, quas odit tempora ullam!",
   },
   // more data here
 ];

 return {
   props: {
      posts,
      total: 20
   },
 };
}

const Home: NextPage<HomeProps> = ({ posts }) => {
 const {
   isLoading,
   loadMoreCallback,
   hasDynamicPosts,
   dynamicPosts,
   isLastPage,
 } = useInfiniteScroll(posts);

 return (
   <HomePage
     posts={hasDynamicPosts ? dynamicPosts : posts}
     isLoading={isLoading}
     loadMoreCallback={loadMoreCallback}
     isLastPage={isLastPage}
   />
 );
};

export default Home;
Вход в полноэкранный режим Выход из полноэкранного режима

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

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

Создание пользовательского крючка

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

// useInfiniteScroll.ts
export interface UseInfiniteScroll {
 isLoading: boolean;
 loadMoreCallback: (el: HTMLDivElement) => void;
 hasDynamicPosts: boolean;
 dynamicPosts: BlogPost[];
 isLastPage: boolean;
}

export const useInfiniteScroll = (posts: BlogPost[]): UseInfiniteScroll => {
 const [isLoading, setIsLoading] = useState(false);
 const [page, setPage] = useState(1);
 const [hasDynamicPosts, setHasDynamicPosts] = useState(false);
 const [dynamicPosts, setDynamicPosts] = useState<BlogPost[]>(posts);
 const [isLastPage, setIsLastPage] = useState(false);
 const observerRef = useRef<IntersectionObserver>();
 const loadMoreTimeout: NodeJS.Timeout = setTimeout(() => null, 500);
 const loadMoreTimeoutRef = useRef<NodeJS.Timeout>(loadMoreTimeout);

 const handleObserver = useCallback(
   (entries: any[]) => {
     const target = entries[0];
     if (target.isIntersecting) {
       setIsLoading(true);
       clearTimeout(loadMoreTimeoutRef.current);

       // this timeout debounces the intersection events
       loadMoreTimeoutRef.current = setTimeout(() => {
         axios.get(`/api/posts/${page}`).then((resp) => {
           setPage(page + 1);
           const newPosts = resp?.data["posts"];

           if (newPosts?.length) {
             const newDynamicPosts = [...dynamicPosts, ...newPosts];
             setDynamicPosts(newDynamicPosts);
             setIsLastPage(newDynamicPosts?.length === resp?.data["total"]);
             setHasDynamicPosts(true);
             setIsLoading(false);
           }
         });
       }, 500);
     }
   },
   [loadMoreTimeoutRef, setIsLoading, page, dynamicPosts]
 );

 const loadMoreCallback = useCallback(
   (el: HTMLDivElement) => {
     if (isLoading) return;
     if (observerRef.current) observerRef.current.disconnect();

     const option: IntersectionObserverInit = {
       root: null,
       rootMargin: "0px",
       threshold: 1.0,
     };
     observerRef.current = new IntersectionObserver(handleObserver, option);

     if (el) observerRef.current.observe(el);
   },
   [handleObserver, isLoading]
 );

 return {
   isLoading,
   loadMoreCallback,
   hasDynamicPosts,
   dynamicPosts,
   isLastPage,
 };
};
Вход в полноэкранный режим Выход из полноэкранного режима

Метод loadMoreCallback — это то, что мы будем использовать для определения того, «пересек» ли пользователь данный элемент. Как вы уже догадались, волшебный API браузера, помогающий нам в этом, называется «Intersection Observer». Мы передаем метод обработчика handleObserver и параметры инициализации в IntersectionObserver, который будет обрабатывать испускаемые события. Если пользователь пересекает наш целевой элемент, то target.isIntersecting будет истинным и будет сделан вызов API для получения дополнительных данных.

Обратите внимание, что вышеупомянутый вызов API обернут в таймаут. Intersection Observer может вызвать несколько пересекающихся событий, когда пользователь достигает цели, поэтому мы хотим отменить эти события, чтобы избежать ненужных вызовов нашего API. Для этого мы выполняем вызов API, только если в течение последних 500 миллисекунд не произошло никаких других пересекающихся событий.

Добавление пересекающегося компонента

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

// Loader.tsx
type LoaderProps = Pick<
 UseInfiniteScroll,
 "isLoading" | "loadMoreCallback" | "isLastPage"
>;

export const Loader = ({
 isLoading,
 isLastPage,
 loadMoreCallback,
}: LoaderProps) => {
 if (isLoading) return <p>Loading...</p>;

 if (isLastPage) return <p>End of content</p>;

 return <div ref={loadMoreCallback}>load more callback</div>;
};
Вход в полноэкранный режим Выход из полноэкранного режима

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

// HomePage.tsx
type HomePageProps = Pick<
 UseInfiniteScroll,
 "isLoading" | "loadMoreCallback" | "isLastPage"
> & {
 posts: BlogPost[];
};

export const HomePage = ({
 posts,
 isLoading,
 loadMoreCallback,
 isLastPage,
}: HomePageProps) => {
 return (
   <main className={styles.container}>
     <h1 className={styles.heading}>Infinite Scroll Demo</h1>
     <p>It is a very nice app</p>
     {posts.map((post) => (
       <div className={styles.blogPost} key={post.id}>
         <img src="https://picsum.photos/500" alt="random image" />
         <div>
           <h2>{post.title}</h2>
           <p>{post.description}</p>
         </div>
       </div>
     ))}

     <Loader
       isLoading={isLoading}
       isLastPage={isLastPage}
       loadMoreCallback={loadMoreCallback}
     />
   </main>
 );
};
Вход в полноэкранный режим Выход из полноэкранного режима

Завершение примера с бесконечной прокруткой в React

Вот и все! С помощью пары компонентов и пользовательского хука вы можете создать свой собственный эффект бесконечной прокрутки с помощью React, TypeScript и NextJS.

Учитывая его повсеместное распространение в потребительском программном обеспечении (например, на сайтах социальных сетей), бесконечная прокрутка стала основной стратегией пагинации для многих компаний. Так что если вам до сих пор удавалось избегать бесконечной прокрутки, я бы не стал делать ставку на продолжение этой тенденции. Когда вас неизбежно попросят добавить эту функцию в приложение, я надеюсь, что это руководство вам поможет.

Оцените статью
devanswers.ru
Добавить комментарий