Pagination и бесконечная прокрутка

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

Пагинация

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

Основная структура

Для реализации пагинации в Solid.js потребуется несколько компонентов:

  • Состояние текущей страницы: это переменная, отслеживающая текущую страницу данных.
  • Запрос данных: функция, которая выполняет запрос на сервер с учётом текущей страницы.
  • Рендеринг данных: отображение данных на странице с учётом полученных данных.
  • Кнопки навигации: элементы управления для перехода между страницами.

Пример реализации пагинации:

import { createSignal, createEffect } from "solid-js";

function PaginatedList() {
  const [page, setPage] = createSignal(1);
  const [data, setData] = createSignal([]);
  
  createEffect(() => {
    fetch(`https://api.example.com/data?page=${page()}`)
      .then(response => response.json())
      .then(result => setData(result.items));
  });

  return (
    <div>
      <ul>
        {data().map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
      <div>
        <button onCl ick={() => setPage(page() - 1)} disabled={page() === 1}>Previous</button>
        <button onCl ick={() => setPage(page() + 1)}>Next</button>
      </div>
    </div>
  );
}

В этом примере:

  1. createSignal используется для отслеживания состояния страницы (page) и данных (data).
  2. createEffect отслеживает изменения страницы и выполняет запрос на сервер с новым значением страницы.
  3. Кнопки “Previous” и “Next” управляют состоянием страницы и позволяют переключаться между страницами.

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

Оптимизация с помощью кэширования

Чтобы избежать повторных запросов на сервер при переходе по страницам, можно внедрить кэширование. Один из способов — сохранять данные для уже загруженных страниц в локальном состоянии. Например:

import { createSignal, createEffect } from "solid-js";

function PaginatedList() {
  const [page, setPage] = createSignal(1);
  const [data, setData] = createSignal([]);
  const [cache, setCache] = createSignal({});

  createEffect(() => {
    const currentPage = page();
    if (cache()[currentPage]) {
      setData(cache()[currentPage]);
    } else {
      fetch(`https://api.example.com/data?page=${currentPage}`)
        .then(response => response.json())
        .then(result => {
          setCache(prev => ({ ...prev, [currentPage]: result.items }));
          setData(result.items);
        });
    }
  });

  return (
    <div>
      <ul>
        {data().map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
      <div>
        <button onCl ick={() => setPage(page() - 1)} disabled={page() === 1}>Previous</button>
        <button onCl ick={() => setPage(page() + 1)}>Next</button>
      </div>
    </div>
  );
}

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

Бесконечная прокрутка

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

Структура бесконечной прокрутки

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

Пример реализации бесконечной прокрутки:

import { createSignal, createEffect, onCleanup } from "solid-js";

function InfiniteScroll() {
  const [data, setData] = createSignal([]);
  const [page, setPage] = createSignal(1);
  const [loading, setLoading] = createSignal(false);

  const loadData = () => {
    if (loading()) return;
    setLoading(true);

    fetch(`https://api.example.com/data?page=${page()}`)
      .then(response => response.json())
      .then(result => {
        setData(prev => [...prev, ...result.items]);
        setPage(page() + 1);
        setLoading(false);
      });
  };

  const handleScroll = () => {
    const scrollPosition = window.scrollY + window.innerHeight;
    const bottomPosition = document.documentElement.scrollHeight;

    if (scrollPosition >= bottomPosition - 100) {
      loadData();
    }
  };

  createEffect(() => {
    loadData();
    window.addEventListener("scroll", handleScroll);
    
    onCleanup(() => {
      window.removeEventListener("scroll", handleScroll);
    });
  });

  return (
    <div>
      <ul>
        {data().map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
      {loading() && <div>Loading...</div>}
    </div>
  );
}

Здесь:

  • При достижении конца страницы (scrollPosition >= bottomPosition - 100), выполняется загрузка данных с сервера.
  • setLoading(true) предотвращает повторные запросы, пока данные не будут загружены.
  • onCleanup используется для удаления обработчика события прокрутки при уничтожении компонента.
Снижение нагрузки на сервер

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

Пример с дебаунсингом:

import { createSignal, createEffect, onCleanup } from "solid-js";

function InfiniteScroll() {
  const [data, setData] = createSignal([]);
  const [page, setPage] = createSignal(1);
  const [loading, setLoading] = createSignal(false);

  let timeout;

  const loadData = () => {
    if (loading()) return;
    setLoading(true);

    fetch(`https://api.example.com/data?page=${page()}`)
      .then(response => response.json())
      .then(result => {
        setData(prev => [...prev, ...result.items]);
        setPage(page() + 1);
        setLoading(false);
      });
  };

  const handleScroll = () => {
    if (timeout) clearTimeout(timeout);

    timeout = setTimeout(() => {
      const scrollPosition = window.scrollY + window.innerHeight;
      const bottomPosition = document.documentElement.scrollHeight;

      if (scrollPosition >= bottomPosition - 100) {
        loadData();
      }
    }, 200);
  };

  createEffect(() => {
    loadData();
    window.addEventListener("scroll", handleScroll);
    
    onCleanup(() => {
      window.removeEventListener("scroll", handleScroll);
    });
  });

  return (
    <div>
      <ul>
        {data().map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
      {loading() && <div>Loading...</div>}
    </div>
  );
}

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

Заключение

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