Подпишитесь на мою рассылку сейчас по адресу http://jauyeung.net/subscribe/.

Подпишитесь на меня в Twitter по адресу https://twitter.com/AuMayeung

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

Чтобы добавить бесконечную прокрутку в приложение, мы можем использовать такую ​​библиотеку, как react-infinite-scroller. Он обеспечивает бесконечную прокрутку, которую можно легко включить в любое приложение React.

В этой истории мы создадим приложение, которое будет получать изображения и видео из API Pixabay, расположенного по адресу https://pixabay.com/. Чтобы использовать API, мы регистрируемся для получения ключа API, а затем мы можем вызывать API 5000 раз в час.

Чтобы начать сборку нашего приложения, мы запускаем npx create-react-app image-app. Это создаст папку проекта и файлы, необходимые для создания нашего приложения.

Далее нам нужно добавить несколько библиотек. Нам нужен HTTP-клиент для выполнения запросов и Bootstrap для стилизации нашего приложения, Formik и Yup для проверки формы, пакет querystring для кодирования строки запроса из объектов, response-infinite-scroller для бесконечной прокрутки и React Router для маршрутизации URL-адресов на наши страницы. . Запускаем npm i axios bootstrap formik querystring react-bootstrap react-router-dom yup react-infinite-scroller для установки библиотек.

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

import React from 'react';
import { Router, Route } from "react-router-dom";
import HomePage from './HomePage';
import { createBrowserHistory as createHistory } from 'history'
import Navbar from 'react-bootstrap/Navbar';
import Nav from 'react-bootstrap/Nav';
import './App.css';
import ImageSearchPage from './ImageSearchPage';
import VideoSearchPage from './VideoSearchPage';
const history = createHistory();
function App() {
  return (
    <div className="App">
      <Router history={history}>
        <Navbar bg="primary" expand="lg" variant="dark" >
          <Navbar.Brand href="/">Image Gallery App</Navbar.Brand>
          <Navbar.Toggle aria-controls="basic-navbar-nav" />
          <Navbar.Collapse id="basic-navbar-nav">
            <Nav className="mr-auto">
              <Nav.Link href="/">Home</Nav.Link>
              <Nav.Link href="/imagesearch">Image Search</Nav.Link>
              <Nav.Link href="/videosearch">Video Search</Nav.Link>
            </Nav>
          </Navbar.Collapse>
        </Navbar>
        <Route path="/" exact component={HomePage} />
        <Route path="/imagesearch" exact component={ImageSearchPage} />
        <Route path="/videosearch" exact component={VideoSearchPage} />
      </Router>
    </div>
  );
}
export default App;

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

Затем мы создаем HomePage.js для создания нашей домашней страницы, которую мы помещаем в папку src. В HomePage.js мы добавляем следующее:

import React from 'react';
import { useState, useEffect } from 'react';
import './HomePage.css';
import InfiniteScroll from 'react-infinite-scroller';
import Figure from 'react-bootstrap/Figure'
import { getPhotos } from './requests';
let page = 0;
function HomePage() {
    const [items, setItems] = useState([]);
    const [initialized, setInitialized] = useState(false);
    const [totalHits, setTotalHits] = useState(0);
    const getNewPhotos = async () => {
        page++;
        const response = await getPhotos(page);
        setItems(items.concat(response.data.hits));
        setTotalHits(response.data.totalHits);
        setInitialized(true);
    }
    useEffect(() => {
        if (!initialized) {
            getNewPhotos();
        }
    });
    return (
        <div className="HomePage">
            <InfiniteScroll
                pageStart={page}
                loadMore={getNewPhotos}
                hasMore={totalHits > items.length}
                threshold={100}
            >
                {items.map((i, index) =>
                    <Figure key={index}>
                        <Figure.Image
                            width={window.innerWidth / 3.5}
                            src={i.previewURL}
                        />
                    </Figure>
                )}
            </InfiniteScroll>
        </div>
    );
}
export default HomePage;

Здесь мы использовали библиотеку react-infinite-scroller, чтобы добавить бесконечную прокрутку. Все очень просто. Все, что мы делаем, это оборачиваем элементы, которые хотим иметь бесконечную прокрутку, в компонент InfiniteScroll, а затем добавляем обработчик для загрузки следующих элементов. В данном случае для этого у нас есть функция getNewPhotos. Обратите внимание, что мы должны увеличивать номер страницы перед загрузкой новых элементов и добавлением в массив items. Свойство hasMore является логическим, поэтому, чтобы продолжать загружать элементы до тех пор, пока у нас ничего не останется, мы используем totalHits > items.length как выражение для hasMore. getNewPhotos установите totalHits и items.

В Homepage.css в той же папке, и мы добавляем:

.figure {
  margin: 5px !important;
}
.HomePage {
  text-align: center;
}

Это добавляет нашим изображениям поля.

Все файлы будут в папке src, если не указано иное. Добавляем файл ImageSearchPage.js для страницы поиска изображений. Там мы добавляем:

import React from 'react';
import { Formik } from 'formik';
import Form from 'react-bootstrap/Form';
import Col from 'react-bootstrap/Col';
import Button from 'react-bootstrap/Button';
import * as yup from 'yup';
import './ImageSearchPage.css';
import { searchPhotos } from './requests';
import { useState, useEffect } from 'react';
import InfiniteScroll from 'react-infinite-scroller';
import Figure from 'react-bootstrap/Figure'
let page = 1;
const schema = yup.object({
  query: yup.string().required('Query is required'),
});
function ImageSearchPage() {
  const [items, setItems] = useState([]);
  const [totalHits, setTotalHits] = useState(0);
  const [keyword, setKeyword] = useState('');
  const handleSubmit = async (evt) => {
    const isValid = await schema.validate(evt);
    if (!isValid) {
      return;
    }
    const data = {
      q: encodeURIComponent(evt.query),
      image_type: 'photo',
      page
    }
    const response = await searchPhotos(data);
    setTotalHits(response.data.totalHits);
    setItems(items.concat(response.data.hits));
    setKeyword(evt.query);
  }
  const getMorePhotos = async () => {
    page++;
    const data = {
      q: encodeURIComponent(keyword),
      image_type: 'photo',
      page
    }
    const response = await searchPhotos(data);
    setTotalHits(response.data.totalHits);
    setItems(items.concat(response.data.hits));
  }
  return (
    <div className="ImageSearchPage">
      <Formik
        validationSchema={schema}
        onSubmit={handleSubmit}
      >
        {({
          handleSubmit,
          handleChange,
          handleBlur,
          values,
          touched,
          isInvalid,
          errors,
        }) => (
            <Form noValidate onSubmit={handleSubmit}>
              <Form.Row>
                <Form.Group as={Col} md="12" controlId="firstName">
                  <Form.Label>
                    <h4>Image Search</h4>
                  </Form.Label>
                  <Form.Control
                    type="text"
                    name="query"
                    placeholder="Keyword"
                    value={values.query || ''}
                    onChange={handleChange}
                    isInvalid={touched.description && errors.query}
                  />
                  <Form.Control.Feedback type="invalid">
                    {errors.query}
                  </Form.Control.Feedback>
                </Form.Group>
              </Form.Row>
              <Button type="submit">Search</Button>
            </Form>
          )}
      </Formik>
      <InfiniteScroll
        pageStart={page}
        loadMore={getMorePhotos}
        hasMore={totalHits > items.length}
        threshold={100}
      >
        {items.map((i, index) =>
          <Figure key={index}>
            <Figure.Image
              width={window.innerWidth / 3.5}
              src={i.previewURL}
            />
          </Figure>
        )}
      </InfiniteScroll>
    </div>
  );
}
export default ImageSearchPage;

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

Затем создаемImageSearchPage.css и добавляем:

.ImageSearchPage{
    padding: 20px;
}

Это добавляет к странице отступ.

Затем мы создаем requests.js и добавляем:

const APIURL = 'https://pixabay.com/api';
const axios = require('axios');
const querystring = require('querystring');
const APIKEY = 'Pixabay API key';
export const getPhotos = (page = 1) => axios.get(`${APIURL}/?key=${APIKEY}&page=${page}`);
export const searchPhotos = (data) => {
    data['key'] = APIKEY;
    return axios.get(`${APIURL}/?${querystring.encode(data)}`);
}
export const searchVideos = (data) => {
    data['key'] = APIKEY;
    return axios.get(`${APIURL}/videos/?${querystring.encode(data)}`);
}

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

Далее мы создаем страницу поиска видео. Мы создаем файл с именем VideoSearchPage.js и добавляем:

import React from 'react';
import './VideoSearchPage.css';
import { searchVideos } from './requests';
import { Formik } from 'formik';
import Form from 'react-bootstrap/Form';
import Col from 'react-bootstrap/Col';
import Button from 'react-bootstrap/Button';
import * as yup from 'yup';
import './ImageSearchPage.css';
import { useState } from 'react';
import Figure from 'react-bootstrap/Figure'
const schema = yup.object({
  query: yup.string().required('Query is required'),
});
function VideoSearchPage() {
  const [items, setItems] = useState([]);
  const handleSubmit = async (evt) => {
    const isValid = await schema.validate(evt);
    if (!isValid) {
      return;
    }
    const data = {
      q: encodeURIComponent(evt.query),
      page: 1
    }
    const response = await searchVideos(data);
    setItems(items.concat(response.data.hits));
  }
  return (
    <div className="ImageSearchPage">
      <Formik
        validationSchema={schema}
        onSubmit={handleSubmit}
      >
        {({
          handleSubmit,
          handleChange,
          handleBlur,
          values,
          touched,
          isInvalid,
          errors,
        }) => (
            <Form noValidate onSubmit={handleSubmit}>
              <Form.Row>
                <Form.Group as={Col} md="12" controlId="firstName">
                  <Form.Label>
                    <h4>Video Search</h4>
                  </Form.Label>
                  <Form.Control
                    type="text"
                    name="query"
                    placeholder="Keyword"
                    value={values.query || ''}
                    onChange={handleChange}
                    isInvalid={touched.description && errors.query}
                  />
                  <Form.Control.Feedback type="invalid">
                    {errors.query}
                  </Form.Control.Feedback>
                </Form.Group>
              </Form.Row>
              <Button type="submit">Search</Button>
            </Form>
          )}
      </Formik>
      {items.map((i, index) =>
        <Figure key={index}>
          <video
            width={window.innerWidth / 3.5}
          >
            <source src={i.videos.tiny.url} type="video/mp4" />
          </video>
        </Figure>
      )}
    </div>
  );
}
export default VideoSearchPage;

Это похоже на страницу поиска изображений, за исключением того, что у нас нет бесконечной прокрутки, а тег img заменен тегом video.

Наконец, в public/index.html у нас есть:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="Web site created using create-react-app" />
  <link rel="apple-touch-icon" href="logo192.png" />
  <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
  <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
  <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
  <title>Image Gallery App</title>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous" />
</head>
<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
  <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
</body>
</html>

Мы добавили:

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous" />

Это добавляет стили Bootstrap в наш код.

После всего этого у нас есть: