react 分页
Often times, we get involved in building web apps in which we are required to fetch large sets of data records from a remote server, API or some database sitting somewhere. If you are building a payment system for example, it could be fetching thousands of transactions. If it is a social media app, it could be fetching tonnes of user comments, profiles or activities. Whichever it is, there are a couple of methods for handling the data such that it doesn't become overwhelming to the end-user interacting with the app.
通常,我们会参与构建Web应用程序,在这些应用程序中,我们需要从远程服务器,API或某个地方的某些数据库中获取大量数据记录。 例如,如果您要建立一个支付系统,它可能会获取数千笔交易。 如果它是社交媒体应用程序,则可能会获取大量用户评论,个人资料或活动。 无论是哪种方法,都有两种方法可以处理数据,以使数据不会被最终用户与应用程序进行交互所淹没。
One of the popular methods for handling large datasets on the view is by using the infinite scrolling technique - where more data is loaded in chunks as the user continues scrolling very close to the end of the page. This is the technique used in displaying search results in Google Images. It is also used in so many social media platforms like Facebook - displaying posts on timeline, Twitter - showing recent tweets, etc.
在视图上处理大型数据集的一种流行方法是使用无限滚动技术 -当用户继续滚动到页面末尾时,更多数据以块的形式加载。 这是用于在Google图片中显示搜索结果的技术。 它也用在许多社交媒体平台(如Facebook)中-在时间轴上显示帖子,在Twitter-显示最近的推文等。
Another known method for handling large datasets is using pagination. Pagination works effectively when you already know the size of the dataset(the total number of records in the dataset) upfront. Secondly, you only load the required chunk of data from the total dataset based on the end-users interaction with the pagination control. This is the technique used in displaying search results in Google Search.
处理大型数据集的另一种已知方法是使用分页 。 当您已经预先知道数据集的大小(数据集中的记录总数)时,分页有效。 其次,仅基于最终用户与分页控件的交互从总数据集中加载所需的数据块。 这是在Google搜索中显示搜索结果的技术。
In this tutorial, we will see how to build a custom pagination component with React for paginating large datasets. In order to keep things as simple as possible, we will build a paginated view of the countries in the world - of which we already have the data of all the countries upfront.
在本教程中,我们将看到如何使用React构建自定义的分页组件以对大型数据集进行分页。 为了使事情尽可能简单,我们将对世界各国进行分页查看-我们已经预先获取了所有国家的数据。
Here is a demo of what we will be building in this tutorial.
这是我们将在本教程中构建的演示。
Before getting started, you need to ensure that you have Node already installed on your machine. I will also recommend that you install the Yarn package manager on your machine, since we will be using it for package management instead of npm that ships with Node. You can follow this Yarn installation guide to install yarn
on your machine.
在开始之前,您需要确保计算机上已经安装了Node 。 我还将建议您在计算机上安装Yarn软件包管理器,因为我们将使用它来进行软件包管理,而不是Node附带的npm 。 您可以按照此纱线安装指南在机器上安装yarn
。
We will create the boilerplate code for our React app using the create-react-app
command-line package. You also need to ensure that it is installed globally on your machine. If you are using npm >= 5.2
then you may not need to install create-react-app
as a global dependency since we can use the npx
command.
我们将使用create-react-app
命令行软件包为React应用创建样板代码。 您还需要确保将其全局安装在您的计算机上。 如果您使用的是npm >= 5.2
那么您可能不需要安装create-react-app
作为全局依赖项,因为我们可以使用npx
命令。
Finally, this tutorial assumes that you are already familiar with React. If that is not the case, you can check the React Documentation to learn more about React.
最后,本教程假定您已经熟悉React。 如果不是这种情况,可以查看React文档以了解有关React的更多信息。
Start a new React application using the following command. You can name the application however you desire.
使用以下命令启动一个新的React应用程序。 您可以根据需要命名应用程序。
create-react-app react-pagination
npm >= 5.2
npm >= 5.2
If you are using
npm
version5.2
or higher, it ships with an additionalnpx
binary. Using thenpx
binary, you don't need to installcreate-react-app
globally on your machine. You can start a new React application with this simple command:如果您使用的是
npm
5.2
或更高版本,它将附带一个附加的npx
二进制文件。 使用npx
二进制文件,您无需在计算机上全局安装create-react-app
。 您可以使用以下简单命令启动新的React应用程序:npx create-react-app react-pagination
npx create-react-app react-pagination
Next, we will install the dependencies we need for our application. Run the following command to install the required dependencies.
接下来,我们将安装应用程序所需的依赖项。 运行以下命令以安装所需的依赖项。
yarn add bootstrap prop-types react-flags countries-api
yarn add -D npm-run-all node-sass-chokidar
We have installed node-sass-chokidar
as a development dependency for our application to enable us use SASS. For more information about this, see this guide.
我们已将node-sass-chokidar
安装为应用程序的开发依赖项,以使我们能够使用SASS。 有关此的更多信息,请参阅本指南 。
Now open the src
directory and change the file extension of all the .css
files to .scss
. The required .css
files will be compiled by node-sass-chokidar
as we continue.
现在打开src
目录,并将所有.css
文件的文件扩展名更改为.scss
。 继续操作时,所需的.css
文件将由node-sass-chokidar
编译。
Edit the package.json
file and modify the scripts
section to look like the following:
编辑package.json
文件并修改scripts
部分,使其如下所示:
"scripts" : {
"start:js" : "react-scripts start" ,
"build:js" : "react-scripts build" ,
"start" : "npm-run-all -p watch:css start:js" ,
"build" : "npm-run-all build:css build:js" ,
"test" : "react-scripts test --env=jsdom" ,
"eject" : "react-scripts eject" ,
"build:css" : "node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/" ,
"watch:css" : "npm run build:css && node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/ --watch --recursive"
}
We installed the bootstrap
package as a dependency for our application since we will be needing some default styling. We will also be using styles from the Bootstrap pagination component. To include Bootstrap in the application, edit the src/index.js
file and add the following line before every other import
statement.
我们将bootstrap
软件包安装为应用程序的依赖项,因为我们将需要一些默认样式。 我们还将使用Bootstrap分页组件中的样式。 要将Bootstrap包含在应用程序中,请编辑src/index.js
文件,并在所有其他import
语句之前添加以下行。
import "bootstrap/dist/css/bootstrap.min.css" ;
We installed react-flags
as a dependency for our application. In order to get access to the flag icons from our application, we will need to copy the icon images to the public
directory of our application. Run the following commands from your terminal to copy the flag icons.
我们安装了react-flags
作为我们应用程序的依赖项。 为了从我们的应用程序访问标志图标,我们需要将图标图像复制到我们的应用程序的public
目录中。 从终端运行以下命令以复制标志图标。
mkdir -p public/img
cp -R node_modules/react-flags/vendor/flags public/img
If you are on a Windows machine, run the following commands instead:
如果您使用的是Windows计算机,请改为运行以下命令:
mkdir \public\img
xcopy \node_modules\react-flags\vendor\flags \public\img /s /e
components
目录 (The components
directory)We will create the following React components for our application.
我们将为我们的应用程序创建以下React组件。
CountryCard
- This simply renders the name, region and flag of a given country.
CountryCard
这只是呈现给定国家的名称,地区和标志。
Pagination
- This contains the whole logic for building, rendering and switching pages on the pagination control.
Pagination
-包含在分页控件上构建,呈现和切换页面的整个逻辑。
Go ahead and create a components
directory inside the src
directory of the application to house all our components.
继续,在应用程序的src
目录中创建一个components
目录,以容纳我们所有的组件。
Start the application by running the following command with yarn
:
通过对yarn
运行以下命令来启动应用程序:
yarn start
The application is now started and development can begin. Notice that a browser tab has been opened for you with live reloading functionality to keep in sync with changes in the application as you develop.
现在,该应用程序已启动,可以开始开发了。 请注意,已为您打开了具有实时重新加载功能的浏览器选项卡,以在您开发时与应用程序中的更改保持同步。
At this point, the application view should look like the following screenshot:
此时,应用程序视图应类似于以下屏幕截图:
Create a new file CountryCard.js
in the src/components
directory and add the following code snippet to it.
在src/components
目录中创建一个新文件CountryCard.js
,并将以下代码片段添加到其中。
import React from 'react' ;
import PropTypes from 'prop-types' ;
import Flag from 'react-flags' ;
const CountryCard = props => {
const { cca2 : code2 = '' , region = null , name = { } } = props . country || { } ;
return (
< div className = "col-sm-6 col-md-4 country-card" >
< div className = "country-card-container border-gray rounded border mx-2 my-3 d-flex flex-row align-items-center p-0 bg-light" >
< div className = "h-100 position-relative border-gray border-right px-2 bg-white rounded-left" >
< Flag country = { code2 } format = "png" pngSize = { 64 } basePath = "./img/flags" className = "d-block h-100" / >
< / div >
< div className = "px-3" >
< span className = "country-name text-dark d-block font-weight-bold" > { name . common } < / span >
< span className = "country-region text-secondary text-uppercase" > { region } < / span >
< / div >
< / div >
< / div >
)
}
CountryCard . propTypes = {
country : PropTypes . shape ( {
cca2 : PropTypes . string . isRequired ,
region : PropTypes . string . isRequired ,
name : PropTypes . shape ( {
common : PropTypes . string . isRequired
} ) . isRequired
} ) . isRequired
} ;
export default CountryCard ;
The CountryCard
component requires a country
prop that contains the data about the country to be rendered. As seen in the propTypes
for the CountryCard
component, the country
prop object must contain the following data:
CountryCard
组件需要一个country
道具,其中包含有关要渲染的国家的数据。 正如所见propTypes
为CountryCard
部件,所述country
丙对象必须包含以下数据:
cca2
- 2-digit country code cca2
位国家/地区代码 region
- the country region e.g Africa region
-国家地区,例如非洲 name.common
- the common name of the country e.g Nigeria name.common
国家的通用名称,例如尼日利亚 Here is a sample country object:
这是一个示例国家对象:
{
cca2 : "NG" ,
region : "Africa" ,
name : {
common : "Nigeria"
}
}
Also notice how we render the country flag using the react-flags
package. You can check the react-flags
documentation to learn more about the required props and how to use the package.
还要注意我们如何使用react-flags
包渲染国家/地区标志。 您可以查看react-flags
文档以了解有关所需道具以及如何使用软件包的更多信息。
Create a new file Pagination.js
in the src/components
directory and add the following code snippet to it.
在src/components
目录中创建一个新文件Pagination.js
,并将以下代码片段添加到其中。
import React , { Component , Fragment } from 'react' ;
import PropTypes from 'prop-types' ;
class Pagination extends Component {
constructor ( props ) {
super ( props ) ;
const { totalRecords = null , pageLimit = 30 , pageNeighbours = 0 } = props ;
this . pageLimit = typeof pageLimit === 'number' ? pageLimit : 30 ;
this . totalRecords = typeof totalRecords === 'number' ? totalRecords : 0 ;
// pageNeighbours can be: 0, 1 or 2
this . pageNeighbours = typeof pageNeighbours === 'number'
? Math . max ( 0 , Math . min ( pageNeighbours , 2 ) )
: 0 ;
this . totalPages = Math . ceil ( this . totalRecords / this . pageLimit ) ;
this . state = { currentPage : 1 } ;
}
}
Pagination . propTypes = {
totalRecords : PropTypes . number . isRequired ,
pageLimit : PropTypes . number ,
pageNeighbours : PropTypes . number ,
onPageChanged : PropTypes . func
} ;
export default Pagination ;
The Pagination
component can take four special props as specified in the propTypes
object.
Pagination
组件可以采用propTypes
对象中指定的四个特殊道具。
totalRecords
- indicates the total number of records to be paginated. It is required.
totalRecords
指示要分页的记录总数。 它是必需的。
pageLimit
- indicates the number of records to be shown per page. If not specified, it defaults to 30
as defined in the constructor()
.
pageLimit
指示pageLimit
显示的记录数。 如果未指定,则默认值为30
(在constructor()
定义constructor()
。
pageNeighbours
- indicates the number of additional page numbers to show on each side of the current page. The minimum value is 0
and the maximum value is 2
. If not specified, it defaults to 0
as defined in the constructor()
. The following image illustrates the effect of different values of the pageNeighbours
prop:
pageNeighbours
指示要在当前页面的每一侧显示的其他页面号的数量。 最小值为0
,最大值为2
。 如果未指定,则默认为0
(在constructor()
定义constructor()
。 下图说明了pageNeighbours
道具的不同值的影响:
onPageChanged
- is a function that will be called with data of the current pagination state only when the current page changes. onPageChanged
是仅当当前页面更改时才使用当前分页状态的数据调用的函数。 In the constructor()
function, we compute the total pages as follows:
在constructor()
函数中,我们计算总页数如下:
this . totalPages = Math . ceil ( this . totalRecords / this . pageLimit ) ;
Notice that we use Math.ceil()
here to ensure that we get an integer value for the total number of pages. This also ensures that the excess records are captured in the last page, especially in cases where the number of excess records is less than the number of records to be shown per page.
请注意,我们在此处使用Math.ceil()
来确保获得的总页数为整数值。 这也可以确保多余的记录被捕获在最后一页中,特别是在多余的记录数少于每页要显示的记录数的情况下。
Finally, we initialize the state with the currentPage
property set to 1
. We need this state property to internally keep track of the currently active page.
最后,我们将currentPage
属性设置为1
来初始化状态。 我们需要此状态属性来内部跟踪当前活动页面。
Next, we will go ahead and create the method for generating the page numbers. Modify the Pagination
component as shown in the following code snippet:
接下来,我们将继续创建用于生成页码的方法。 修改Pagination
组件,如以下代码片段所示:
const LEFT_PAGE = 'LEFT' ;
const RIGHT_PAGE = 'RIGHT' ;
/**
* Helper method for creating a range of numbers
* range(1, 5) => [1, 2, 3, 4, 5]
*/
const range = ( from , to , step = 1 ) => {
let i = from ;
const range = [ ] ;
while ( i <= to ) {
range . push ( i ) ;
i += step ;
}
return range ;
}
class Pagination extends Component {
/**
* Let's say we have 10 pages and we set pageNeighbours to 2
* Given that the current page is 6
* The pagination control will look like the following:
*
* (1) < {4 5} [6] {7 8} > (10)
*
* (x) => terminal pages: first and last page(always visible)
* [x] => represents current page
* {...x} => represents page neighbours
*/
fetchPageNumbers = ( ) => {
const totalPages = this . totalPages ;
const currentPage = this . state . currentPage ;
const pageNeighbours = this . pageNeighbours ;
/**
* totalNumbers: the total page numbers to show on the control
* totalBlocks: totalNumbers + 2 to cover for the left(<) and right(>) controls
*/
const totalNumbers = ( this . pageNeighbours * 2 ) + 3 ;
const totalBlocks = totalNumbers + 2 ;
if ( totalPages > totalBlocks ) {
const startPage = Math . max ( 2 , currentPage - pageNeighbours ) ;
const endPage = Math . min ( totalPages - 1 , currentPage + pageNeighbours ) ;
let pages = range ( startPage , endPage ) ;
/**
* hasLeftSpill: has hidden pages to the left
* hasRightSpill: has hidden pages to the right
* spillOffset: number of hidden pages either to the left or to the right
*/
const hasLeftSpill = startPage > 2 ;
const hasRightSpill = ( totalPages - endPage ) > 1 ;
const spillOffset = totalNumbers - ( pages . length + 1 ) ;
switch ( true ) {
// handle: (1) < {5 6} [7] {8 9} (10)
case ( hasLeftSpill && ! hasRightSpill ) : {
const extraPages = range ( startPage - spillOffset , startPage - 1 ) ;
pages = [ LEFT_PAGE , ... extraPages , ... pages ] ;
break ;
}
// handle: (1) {2 3} [4] {5 6} > (10)
case ( ! hasLeftSpill && hasRightSpill ) : {
const extraPages = range ( endPage + 1 , endPage + spillOffset ) ;
pages = [ ... pages , ... extraPages , RIGHT_PAGE ] ;
break ;
}
// handle: (1) < {4 5} [6] {7 8} > (10)
case ( hasLeftSpill && hasRightSpill ) :
default : {
pages = [ LEFT_PAGE , ... pages , RIGHT_PAGE ] ;
break ;
}
}
return [ 1 , ... pages , totalPages ] ;
}
return range ( 1 , totalPages ) ;
}
}
Here, we first define two constants: LEFT_PAGE
and RIGHT_PAGE
. These constants will be used to indicate points where we have page controls for moving left and right respectively.
在这里,我们首先定义两个常量: LEFT_PAGE
和RIGHT_PAGE
。 这些常数将用于指示我们具有分别用于向左和向右移动的页面控件的点。
We also define a helper range()
function that can help us generate ranges of numbers. If you use a utility library like Lodash in your project, then you can use the _.range()
function provided by Lodash instead. The following code snippet shows the difference between the range()
function we just defined and the one from Lodash:
我们还定义了一个helper range()
函数,可以帮助我们生成数字范围。 如果使用工具库一样Lodash在你的项目,那么你可以使用_.range()
由Lodash提供替代功能。 以下代码段显示了我们刚刚定义的range()
函数与Lodash中的函数之间的区别:
range ( 1 , 5 ) ; // returns [1, 2, 3, 4, 5]
_ . range ( 1 , 5 ) ; // returns [1, 2, 3, 4]
Next, we define the fetchPageNumbers()
method in the Pagination
class. This method handles the core logic for generating the page numbers to be shown on the pagination control. We want the first page and last page to always be visible.
接下来,我们在Pagination
类中定义fetchPageNumbers()
方法。 此方法处理用于生成要在分页控件上显示的页码的核心逻辑。 我们希望首页和最后一页始终可见。
First, we define a couple of variables. totalNumbers
represents the total page numbers that will be shown on the control. totalBlocks
represents the total page numbers to be shown plus two additional blocks for left and right page indicators.
首先,我们定义几个变量。 totalNumbers
表示将在控件上显示的总页数。 totalBlocks
表示要显示的总页数,另外两个用于左右页面指示器的块。
If totalPages
is not greater than totalBlocks
, we simply return a range of numbers from 1
to totalPages
. Otherwise, we return the array of page numbers, with LEFT_PAGE
and RIGHT_PAGE
at points where we have pages spilling to the left and right respectively.
如果totalPages
不大于totalBlocks
,我们只返回从1
到totalPages
的数字范围。 否则,我们将返回页码数组,其中LEFT_PAGE
和RIGHT_PAGE
位于页面分别向左和向右溢出的位置。
Notice however that our pagination control ensures that the first page and last page are always visible. The left and right page controls appear inwards.
但是请注意,我们的分页控件可确保第一页和最后一页始终可见。 左右页面控件向内显示。
Now we will go ahead and add the render()
method to enable us render the pagination control. Modify the Pagination
component as shown in the following snippet:
现在,我们将继续添加render()
方法,以使我们能够渲染分页控件。 修改Pagination
组件,如以下代码片段所示:
class Pagination extends Component {
render ( ) {
if ( ! this . totalRecords || this . totalPages === 1 ) return null ;
const { currentPage } = this . state ;
const pages = this . fetchPageNumbers ( ) ;
return (
< Fragment >
< nav aria - label = "Countries Pagination" >
< ul className = "pagination" >
{ pages . map ( ( page , index ) => {
if ( page === LEFT_PAGE ) return (
< li key = { index } className = "page-item" >
< a className = "page-link" href = "#" aria - label = "Previous" onClick = { this . handleMoveLeft } >
< span aria - hidden = "true" > & laquo ; < / span >
< span className = "sr-only" > Previous < / span >
< / a >
< / li >
) ;
if ( page === RIGHT_PAGE ) return (
< li key = { index } className = "page-item" >
< a className = "page-link" href = "#" aria - label = "Next" onClick = { this . handleMoveRight } >
< span aria - hidden = "true" > & raquo ; < / span >
< span className = "sr-only" > Next < / span >
< / a >
< / li >
) ;
return (
< li key = { index } className = { `page-item ${ currentPage === page ? ' active' : '' } ` } >
< a className = "page-link" href = "#" onClick = { this . handleClick ( page ) } > { page } < / a >
< / li >
) ;
} ) }
< / ul >
< / nav >
< / Fragment >
) ;
}
}
Here, we generate the page numbers array
by calling the fetchPageNumbers()
method we created earlier. We then render each page number using Array.prototype.map()
. Notice that we register click event handlers on each rendered page number to handle clicks.
在这里,我们通过调用我们之前创建的fetchPageNumbers()
方法来生成页码array
。 然后,我们使用Array.prototype.map()
呈现每个页码。 请注意,我们在每个呈现的页码上注册了click事件处理程序,以处理单击。
Also notice that the pagination control will not be rendered if the totalRecords
prop was not passed in correctly to the Pagination
component or in cases where there is only 1
page.
还要注意的是,分页控制不会,如果所呈现的totalRecords
道具不是在正确的传递Pagination
组件或在情况下,有只有1
页。
Finally, we will define the event handler methods. Modify the Pagination
component as shown in the following snippet:
最后,我们将定义事件处理程序方法。 修改Pagination
组件,如以下代码片段所示:
class Pagination extends Component {
componentDidMount ( ) {
this . gotoPage ( 1 ) ;
}
gotoPage = page => {
const { onPageChanged = f => f } = this . props ;
const currentPage = Math . max ( 0 , Math . min ( page , this . totalPages ) ) ;
const paginationData = {
currentPage ,
totalPages : this . totalPages ,
pageLimit : this . pageLimit ,
totalRecords : this . totalRecords
} ;
this . setState ( { currentPage } , ( ) => onPageChanged ( paginationData ) ) ;
}
handleClick = page => evt => {
evt . preventDefault ( ) ;
this . gotoPage ( page ) ;
}
handleMoveLeft = evt => {
evt . preventDefault ( ) ;
this . gotoPage ( this . state . currentPage - ( this . pageNeighbours * 2 ) - 1 ) ;
}
handleMoveRight = evt => {
evt . preventDefault ( ) ;
this . gotoPage ( this . state . currentPage + ( this . pageNeighbours * 2 ) + 1 ) ;
}
}
We define the gotoPage()
method that modifies the state and sets the currentPage
to the specified page. It ensures that the page
argument has a minimum value of 1
and a maximum value of the total number of pages. It finally calls the onPageChanged()
function that was passed in as prop, with data indicating the new pagination state.
我们定义gotoPage()
方法,该方法修改状态并将currentPage
设置为指定的页面。 它确保page
参数的最小值为1
,最大值为总页数。 最后,它将调用作为onPageChanged()
传入的onPageChanged()
函数,其中的数据指示新的分页状态。
When the component mounts, we simply go to the first page by calling this.gotoPage(1)
as shown in the componentDidMount()
lifecycle method.
装入组件后,我们只需调用this.gotoPage(1)
转到第一页,如componentDidMount()
生命周期方法所示。
Notice how we use (this.pageNeighbours * 2)
in handleMoveLeft()
and handleMoveRight()
to slide the page numbers to the left and to the right respectively based on the current page number.
请注意,我们如何在handleMoveLeft()
和handleMoveRight()
使用(this.pageNeighbours * 2)
将页码分别基于当前页码向左和向右滑动。
Here is a demo of the interaction we have been able to achieve:
这是我们已经实现的交互的演示:
We will go ahead and modify the App.js
file in the src
directory. The App.js
file should look like the following snippet:
我们将继续修改src
目录中的App.js
文件。 App.js
文件应类似于以下片段:
import React , { Component } from 'react' ;
import Countries from 'countries-api' ;
import './App.css' ;
import Pagination from './components/Pagination' ;
import CountryCard from './components/CountryCard' ;
class App extends Component {
state = { allCountries : [ ] , currentCountries : [ ] , currentPage : null , totalPages : null }
componentDidMount ( ) {
const { data : allCountries = [ ] } = Countries . findAll ( ) ;
this . setState ( { allCountries } ) ;
}
onPageChanged = data => {
const { allCountries } = this . state ;
const { currentPage , totalPages , pageLimit } = data ;
const offset = ( currentPage - 1 ) * pageLimit ;
const currentCountries = allCountries . slice ( offset , offset + pageLimit ) ;
this . setState ( { currentPage , currentCountries , totalPages } ) ;
}
}
export default App ;
Here we initialize the App
component's state with the following attributes:
在这里,我们使用以下属性初始化App
组件的状态:
allCountries
- an array of all the countries in our app. Initialized to an empty array([]
).
allCountries
应用程式中所有国家/地区的阵列。 初始化为空数组( []
)。
currentCountries
- an array of all the countries to be shown on the currently active page. Initialized to an empty array([]
).
currentCountries
当前活动页面上显示的所有国家/地区的数组。 初始化为空数组( []
)。
currentPage
- the page number of the currently active page. Initialized to null
.
currentPage
当前活动页面的页码。 初始化为null
。
totalPages
- the total number of pages for all the country records. Initialized to null
.
totalPages
所有国家/地区记录的总页数。 初始化为null
。
Next, in the componentDidMount()
lifecycle method, we fetch all the world countries using the countries-api
package by invoking Countries.findAll()
. We then update the app state, setting allCountries
to contain all the world countries. You can see the countries-api
documentation to learn more about the package.
接下来,在componentDidMount()
生命周期方法中,我们通过调用Countries.findAll()
来使用countries-api
包来获取世界所有Countries.findAll()
。 然后,我们更新应用状态,将allCountries
设置为包含世界所有国家。 您可以查看countries-api
文档以了解有关该软件包的更多信息。
Finally, we defined the onPageChanged()
method, which will be called each time we navigate to a new page from the pagination control. This method will be passed to the onPageChanged
prop of the Pagination
component.
最后,我们定义了onPageChanged()
方法,该方法将在每次我们从分页控件导航到新页面时调用。 该方法将传递给Pagination
组件的onPageChanged
。
There are two lines that are worth paying attention to in this method. The first is this line:
此方法有两行值得关注。 首先是这一行:
const offset = ( currentPage - 1 ) * pageLimit ;
The offset
value indicates the starting index for fetching the records for the current page. Using (currentPage - 1)
ensures that the offset is zero-based. Let's say for example that we are displaying 25
records per page and we are currently viewing page 5
. Then the offset
will be ((5 - 1) * 25 = 100)
.
offset
值指示用于获取当前页面记录的起始索引。 使用(currentPage - 1)
确保偏移量从零开始。 例如,假设我们每页显示25
条记录,而当前正在查看第5
页。 那么offset
将是((5 - 1) * 25 = 100)
。
In fact, if you are fetching records on demand from a database for example, this is a sample SQL query to show you how offset can be used:
实际上,例如,如果您要从数据库中按需获取记录,则此示例SQL查询向您展示如何使用偏移量:
SELECT * FROM `countries` LIMIT 100, 25
Since, we are not fetching records on demand from a database or any external source, we need a way to extract the required chunk of records to be shown for the current page. That brings us to the second line:
由于我们不是从数据库或任何外部源中按需获取记录,因此,我们需要一种方法来提取要显示在当前页面上的所需记录块。 这使我们进入第二行:
const currentCountries = allCountries . slice ( offset , offset + pageLimit ) ;
Notice here that we use the Array.prototype.slice()
method to extract the required chunk of records from allCountries
by passing the offset
as the starting index for the slice and (offset + pageLimit)
as the index before which to end the slice.
请注意,这里我们使用Array.prototype.slice()
方法通过将offset
作为切片的起始索引,并通过(offset + pageLimit)
作为结束切片之前的索引,从allCountries
提取所需的记录块。
Fetching records in real applications
在实际应用程序中获取记录
In order to keep this tutorial as simple as possible, we did not fetch records from any external source. In a real application, you will probably be fetching records from a database or an API. The logic for fetching the records can easily go into the
onPageChanged()
method of theApp
component.为了使本教程尽可能简单,我们没有从任何外部来源获取记录。 在实际的应用程序中,您可能会从数据库或API中获取记录。 提取记录的逻辑可以轻松进入
App
组件的onPageChanged()
方法。Let's say we have a fictitious API endpoint
/api/countries?page={current_page}&limit={page_limit}
. The following snippet shows how we can fetch countries on demand from the API using theaxios
HTTP package:假设我们有一个虚构的API端点
/api/countries?page={current_page}&limit={page_limit}
。 以下代码段显示了如何使用axios
HTTP包从API中按需获取国家/地区:onPageChanged = data => { const { currentPage, totalPages, pageLimit } = data; axios.get(`/api/countries?page=${ currentPage}&limit=${ pageLimit}`) .then(response => { const currentCountries = response.data.countries; this.setState({ currentPage, currentCountries, totalPages }); }); }
Now we will go ahead and finish up the App
component by adding the render()
method. Modify the App
component accordingly as shown in the following snippet:
现在,我们将通过添加render()
方法完成App
组件。 相应地修改App
组件,如以下代码片段所示:
class App extends Component {
// other methods here ...
render() {
const {
allCountries, currentCountries, currentPage, totalPages } = this.state;
const totalCountries = allCountries.length;
if (totalCountries === 0) return null;
const headerClass = ['text-dark py-2 pr-4 m-0', currentPage ? 'border-gray border-right' : ''].join(' ').trim();
return (
<div className="container mb-5">
<div className="row d-flex flex-row py-5">
<div className="w-100 px-4 py-5 d-flex flex-row flex-wrap align-items-center justify-content-between">
<div className="d-flex flex-row align-items-center">
<h2 className={
headerClass}>
<strong className="text-secondary">{
totalCountries}</strong> Countries
</h2>
{
currentPage && (
<span className="current-page d-inline-block h-100 pl-4 text-secondary">
Page <span className="font-weight-bold">{
currentPage }</span> / <span className="font-weight-bold">{
totalPages }</span>
</span>
) }
</div>
<div className="d-flex flex-row py-4 align-items-center">
<Pagination totalRecords={
totalCountries} pageLimit={
18} pageNeighbours={
1} onPageChanged={
this.onPageChanged} />
</div>
</div>
{
currentCountries.map(country => <CountryCard key={
country.cca3} country={
country} />) }
</div>
</div>
);
}
}
The render()
method is quite straightforward. We render the total number of countries, the current page, the total number of pages, the
control and then the
for each country in the current page.
render()
方法非常简单。 我们呈现当前页面中每个国家的
总数,当前页面,页面总数,
控件,然后是
。
Notice that we passed the onPageChanged()
method we defined earlier to the onPageChanged
prop of the
control. This is very important for capturing page changes from the Pagination
component. Also notice that we are displaying 18
countries per page.
请注意,我们onPageChanged()
先前定义的onPageChanged()
方法传递给
控件的onPageChanged
。 这对于从Pagination
组件捕获页面更改非常重要。 另请注意,我们每页显示18
个国家。
At this point, the app should look like the following screenshot:
此时,该应用程序应类似于以下屏幕截图:
You would have noticed that we have been adding some custom classes to the components we created earlier. We will go ahead and define some style rules for those classes in the src/App.scss
file. The App.scss
file should look like the following snippet:
您可能已经注意到,我们已经向我们先前创建的组件中添加了一些自定义类。 我们将继续在src/App.scss
文件中为这些类定义一些样式规则。 App.scss
文件应类似于以下片段:
/* Declare some variables */
$base-color: #ced4da;
$light-background: lighten(desaturate($base-color, 50%), 12.5%);
.current-page {
font-size: 1.5rem;
vertical-align: middle;
}
.country-card-container {
height: 60px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.country-name {
font-size: 0.9rem;
}
.country-region {
font-size: 0.7rem;
}
.current-page,
.country-name,
.country-region {
line-height: 1;
}
// Override some Bootstrap pagination styles
ul.pagination {
margin-top: 0;
margin-bottom: 0;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
li.page-item.active {
a.page-link {
color: saturate(darken($base-color, 50%), 5%) !important;
background-color: saturate(lighten($base-color, 7.5%), 2.5%) !important;
border-color: $base-color !important;
}
}
a.page-link {
padding: 0.75rem 1rem;
min-width: 3.5rem;
text-align: center;
box-shadow: none !important;
border-color: $base-color !important;
color: saturate(darken($base-color, 30%), 10%);
font-weight: 900;
font-size: 1rem;
&:hover {
background-color: $light-background;
}
}
}
After adding the styles, the app should now look like the following screenshot:
添加样式后,应用程序现在应类似于以下屏幕截图:
In this tutorial, we have been able to create a custom pagination widget in our React application. Although we didn't make calls to any API or interact with any database back-end in this tutorial, it is possible that your application may demand such interactions. You are not in any way limited to the approach used in this tutorial - you can extend it as you wish to suite the requirements of your application.
在本教程中,我们已经能够在React应用程序中创建自定义分页小部件。 尽管在本教程中我们没有调用任何API或与任何数据库后端进行交互,但是您的应用程序可能需要这种交互。 您绝不限于本教程中使用的方法-您可以根据需要来扩展它,以适合您的应用程序的需求。
For the complete sourcecode of this tutorial, checkout the build-react-pagination-demo repository on Github. You can also get a live demo of this tutorial on Code Sandbox.
有关本教程的完整源代码,请检查Github上的build-react-pagination-demo存储库。 您还可以在Code Sandbox上获得本教程的现场演示 。
翻译自: https://scotch.io/tutorials/build-custom-pagination-with-react
react 分页