React 无限滚动教程:使用和不使用库

2025-06-07

React 无限滚动教程:使用和不使用库

本教程最初发布于https://www.devaradise.com/react-infinite-scroll-tutorial

无限滚动是一种现代网页和应用程序设计理念,它会随着用户向下滚动页面而持续加载内容。它改变了分页的功能。

如果您有大量数据需要加载,并且不希望用户点击页码来查看更多数据,那么实现无限滚动是一个不错的选择。它可以提升应用程序的用户体验。

作为开发者,我们可以在任何应用程序中实现无限滚动,包括 React 应用程序。React 无限滚动可以通过两种方式实现:无需库即可手动实现,以及使用无限滚动库。

在这篇文章中,我将向您展示并解释如何在 React 项目中实现无限滚动,使用和不使用库都是如此。两种方法各有优缺点。

替代文本

在开始教程之前,请确保你已经知道如何使用 create-react-app 样板代码初始化一个 React 应用。因为我不会在这里讲解 React 的基础教程,所以我假设你已经理解了。

在本教程中,我们将使用 React 函数式组件和钩子。我们还将使用React-lab来托管演示示例,并使用该项目架构来管理项目文件。

如何在不使用库的情况下实现无限滚动

如果你想让你的 React 项目尽可能轻量,那么不使用任何库来实现 React 无限滚动是最好的选择。如果你需要进行一些自定义,这也是最好的选择。

就我个人而言,我会选择这种方法在我的 React 应用上实现无限滚动。我认为它不需要写太多代码和逻辑。

我们只需要一些状态、一个滚动事件监听器、一个 API 调用服务以及加载数据和放置一些逻辑的函数。

创建组件

假设我们要创建一个实现无限滚动的用户列表页面。因此,我们需要一个组件来实现它。

import React, { useState } from "react";

export default function InfiniteScrollNoLibrary() {

  const [userList, setUserList] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const [noData, setNoData] = useState(false);

  return (
    <div>

      <div className="section">

        {userList.map((user, i) => 
          ( 
          <div className="box m-3 user" key={i}>
            <img src={user.avatar} alt={user.first_name}/>
            <div className="user-details">
              <strong>Email</strong>: {user.email}<br/> 
              <strong>First Name</strong>: {user.first_name}<br/> 
              <strong>Last Name</strong>: {user.last_name}<br/>
            </div>
          </div>
          )
        )}
        {loading ? <div className="text-center">loading data ...</div> : "" }
        {noData ? <div className="text-center">no data anymore ...</div> : "" }    
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

我还没有添加业务逻辑和事件监听器。我先解释一下状态和标记。

要手动实现无限滚动,我们至少需要 4 种状态:

  • userList用于存储来自 API 的用户数据数组。默认为空数组。
  • page计算需要加载的用户列表页面。这有助于我们避免加载和添加相同的数据到列表中。
  • loading在调用 API 时给出加载状态。
  • noData当没有数据时给出无数据状态并停止 API 调用。

正如您在上面的代码中看到的,userList状态将使用 JSX 标记进行循环。每当状态有值map时,还会添加“正在加载...”和“不再有数据...”文本loadingnoDatatrue

创建 API 调用服务

在我向组件添加一些逻辑之前,我首先创建一个用于调用用户数据的服务。

实际上,你可以直接在组件中调用 API,而无需创建服务。但我个人更喜欢将其与组件分离。你可以在我的React 项目结构文章中了解原因。

import axios from 'axios';

export default {

  getList: async function(page) {
    try {
      let url;
      if(page!=null & page > 1) {
        url ="https://reqres.in/api/users?per_page=2&page="+page;
      } else {
        url = "https://reqres.in/api/users?per_page=2";
      }
      const response = await axios.get(url);
      return response.data;
    } catch(error) {
      throw error;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

上面的 getList 函数接受一个page参数,用于根据插入的页码动态更改 URL 字符串。对于虚拟数据,我使用了resreq.in用户 API。

向组件添加一些逻辑

创建服务后,我们现在需要在组件中使用它并添加一些逻辑。完整的组件代码请见下方。之后我会进行解释。

import React, { useState, useEffect } from "react";
import UserService from 'services/UserService';

export default function InfiniteScrollNoLibrary() {

  const [userList, setUserList] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const [noData, setNoData] = useState(false);

  window.onscroll = () => {
    if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) {
      if(!noData) {
        loadUserList(page);
      }
    }
  }

  useEffect(() => {
    loadUserList(page);
  }, []);

  const loadUserList = (page) => {
    setLoading(true);
    setTimeout(() => {
      UserService.getList(page)
        .then((res) => {
          const newPage = page + 1;
          const newList = userList.concat(res.data);
          setUserList(newList);
          setPage(newPage);
          if(res.data.length===0)
            setNoData(true);
        })
        .catch((err) => {
          console.log(err);
        })
        .finally(() =>{
          setLoading(false);
        })
      }
    ,1500);
  }

  return (
    <div>

      <div className="section">

        {userList.map((user, i) => 
          ( 
          <div className="box m-3 user" key={i}>
            <img src={user.avatar} alt={user.first_name}/>
            <div className="user-details">
              <strong>Email</strong>: {user.email}<br/> 
              <strong>First Name</strong>: {user.first_name}<br/> 
              <strong>Last Name</strong>: {user.last_name}<br/>
            </div>
          </div>
          )
        )}
        {loading ?  <div className="text-center">loading data ...</div> : "" }
        {noData ? <div className="text-center">no data anymore ...</div> : "" }    
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

首先,我们导入UserServiceuseEffect挂载组件。稍后我们将在 API 调用函数中使用它们。

上述组件中最重要的代码位于第 11 至 17 行。

window.onscroll = () => {
    if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) {
      if(!noData) {
        loadUserList(page);
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

这是一个监听用户滚动页面的函数。我在里面添加了一个逻辑:“如果用户滚动到页面底部,并且noDatastate 为 false,则加载用户列表”。

当用户刚进入页面但尚未滚动时,我们会在useEffect钩子内加载用户列表。因此,用户数据仍然处于加载状态。

useEffect(() => {
    loadUserList(page);
  }, []);
Enter fullscreen mode Exit fullscreen mode

现在,看一下该loadUserList函数。

const loadUserList = (page) => {
    setLoading(true);
    setTimeout(() => {
      UserService.getList(page)
        .then((res) => {
          const newList = userList.concat(res.data);
          setUserList(newList);

          const newPage = page + 1;
          setPage(newPage);

          if(res.data.length===0)
            setNoData(true);
        })
        .catch((err) => {
          console.log(err);
        })
        .finally(() =>{
          setLoading(false);
        })
      }
    ,1500);
  }
Enter fullscreen mode Exit fullscreen mode

首先,我们将加载状态设置为true在调用 API 时显示“正在加载...”文本。我在这里使用 setTimeout 函数只是为了延迟 API 调用,以便能够看到加载状态。您不必在代码中使用它。

在第 4 行,我调用 UserService 中的 getList 函数并将其传递page给它。如果 API 请求成功,来自 API 的新用户数据将被添加到当前用户列表中(第 6-7 行)。

我们还需要为用户再次滚动时调用的 API 设置新的page状态。你可以在第 9-10 行看到它。

最后,我们创建一个条件来设置noDataState。如果 API 响应为空数组,则表示没有其他数据需要加载。因此,我们将noDataState 设置为true

如果 API 请求返回错误,请在catch部分中捕获该错误。在本例中,我只是通过 console.log 来记录错误。并在finally部分中将loading状态设置为false“再次”,因为请求已结束。

就是这样。现在你可以自己练习一下了。要查看无需库的无限滚动的现场演示,请点击下面的链接。

现场演示

如何实现无限滚动react-infinite-scroller

如果你不想手动实现 React 无限滚动,你仍然可以使用库来实现。市面上有很多用于实现 React 无限滚动的库。

如果您希望代码更简洁,并且希望有一些选项可以轻松自定义,那么使用无限滚动库是最佳选择。大多数 React 无限滚动库都比我之前介绍的手动实现拥有更多的选项和功能。

在本教程中,我使用它react-infinite-scroller,因为它简单易行。事不宜迟,让我们看看如何在你的 React 项目中使用它。

安装并导入 react-infinite-scroller

首先,你应该使用 npm 将 react-infinite-scroller 安装到你的项目中

npm i react-infinite-scroller
Enter fullscreen mode Exit fullscreen mode

要在组件中使用它,只需像这样导入它。

import InfiniteScroll from 'react-infinite-scroller'
Enter fullscreen mode Exit fullscreen mode

在组件中使用 InfiniteScroll

这是完整的组件代码。我在下面进行解释。

import React, { useState } from 'react'
import InfiniteScroll  from 'react-infinite-scroller'
import UserService from 'services/UserService';

export default function InfiniteScrollerWithReactInfiniteScroller() {

  const [userList, setUserList] = useState([]);
  const [hasMoreItems, setHasMoreItems] = useState(true);

  const loadUserList = (page) => {
    setTimeout(() => {
      UserService.getList(page)
      .then((res) => {
        const newList = userList.concat(res.data);
        setUserList(newList);

        if(res.data.length===0) {
          setHasMoreItems(false);
        } else {
          setHasMoreItems(true);
        }
      })
      .catch((err) => {
        console.log(err);
      })

    }, 1500)
  }

  return (
    <div>
      <div className="section">
        <InfiniteScroll
          threshold={0}
          pageStart={0}
          loadMore={loadUserList}
          hasMore={hasMoreItems}
          loader={<div className="text-center">loading data ...</div>}>

            {userList.map((user, i) => 
              ( 
              <div className="box m-3 user" key={i}>
                <img src={user.avatar} alt={user.first_name}/>
                <div className="user-details">
                  <strong>Email</strong>: {user.email}<br/> 
                  <strong>First Name</strong>: {user.first_name}<br/> 
                  <strong>Last Name</strong>: {user.last_name}<br/>
                </div>
              </div>
              )
            )}
        </InfiniteScroll>
        {hasMoreItems ? "" : <div className="text-center">no data anymore ...</div> }    
      </div>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

如你所见,使用库可以减少需要编写的状态和逻辑。我们只需要userList状态来存储用户数据,并将hasMoreItems其传递给<InfiniteScroll/>pageloading状态将由 处理react-infinite-scroll

loadUserList函数中,我们使用了之前手动实现的 UserService。当 API 请求成功时,我们只需要设置新的用户列表(第 14-15 行)并设置hasMoreItems状态(第 17-21 行)。

大多数逻辑都是通过<InfiniteScroll/>循环来处理的userList

<InfiniteScroll
   threshold={0}
   pageStart={0}
   loadMore={loadUserList}
   hasMore={hasMoreItems}
   loader={<div className="text-center">loading data ...</div>}>
      {userList.map((user, i) => 
         ( 
         <div className="box m-3 user" key={i}>
            <img src={user.avatar} alt={user.first_name}/>
            <div className="user-details">
               <strong>Email</strong>: {user.email}<br/> 
               <strong>First Name</strong>: {user.first_name}<br/> 
               <strong>Last Name</strong>: {user.last_name}<br/>
            </div>
         </div>
         )
      )}
</InfiniteScroll>
Enter fullscreen mode Exit fullscreen mode

如你所见,上面 i 用到了一些属性InfiniteScroll。以下是具体解释。

  • threshold是触发新列表加载的页面底部与窗口视口底部之间的距离 - 默认为 250。但我将其设置为 0。
  • pageStart是初始列表对应的页码,默认为, 0 表示第一次加载时, loadMore 会用 来调用 1
  • loadMore(pageToLoad)当用户向下滚动时,我们需要加载新列表,就会调用该方法。该值应该是一个函数。它会将page数字传递给该值。
  • hasMore是一个布尔值,表示是否还有更多项目需要加载。如果 ,则删除事件监听器 false
  • loaderInfiniteScroll 是加载项目时显示的加载器元素 - 您可以使用 InfiniteScroll.setDefaultLoader(loader); 为所有组件设置默认加载器 

要使用更多属性,您可以查看此处的文档。如果您想观看使用 react-infinite-scoller 实现无限滚动的现场演示,请点击下方链接。

现场演示

就这样。希望对你有用。

编码愉快!

文章来源:https://dev.to/syakirurahman/react-infinite-scroll-tutorial-with-and-without-a-library-1abg
PREV
JavaScript 初学者最佳实践
NEXT
React 函数组件与 Hooks:你需要知道的一切