با استفاده از React، صفحه‌ بندی‌ سفارشی‌سازی شده بسازید

گردآوری و تالیف : عرفان کاکایی
تاریخ انتشار : 01 تیر 1397
دسته بندی ها : جاوا اسکریپت

اکثر مواقع، در موقعیتی قرار می‌گیریم که باید یک وب‌اپلیکیشن بسازیم که نیاز است حجم زیادی داده را از یک سرور کنترل از راه دور، یک API یا یک دیتابیس دریافت کند. برای مثال اگر در حل ساخت یک سیستم پرداخت هستید، ممکن است در حال دریافت هزاران تراکنش باشید. اگر در حال ساخت یک شبکه اجتماعی هستید، ممکن استدر حال دریافت چندین کامنت، پروفایل یا فعالیت کاربران (Activity) باشید. هر کاری که می‌خواهید انجام دهید، چند روش وجود دارند که با استفاده از ‌آن‌ها می‌توانید این داده‌ها را به گونه‌ای مدیریت کنید که به اختلال در تعامل با کاربر منجر نشوند.

یکی از روش‌های مدیریت دیتاست‌های بزرگ، استفاده از تکنیک اسکرول بی‌نهایت است، که در آن هرچه کاربر نزدیک تر به پایین صفحه اسکرول می‌کند، داده‌های بیشتری بارگذاری می‌شوند. این تکنیک در نشان دادن نتیجه‌های جستجوی عکس در گوگل استفاده شده است. همچنین این تکنیک در بسیاری پلتفرم‌های شبکه‌های اجتماعی مانند فیس‌بوک برای نشان دادن پست‌ها، و توییتر برای نشان دادن توییت‌ها و... استفاده شده است.

روش دیگر برای مدیریت دیتاست‌های بزرگ نیز استفاده از صفحه‌بندی است. زمانی که از قبل حجم دیتاست (تعداد تمام رکورد‌های موجود در دیتایست) را می‌دانید، این روش بسیار موثر کار می‌کند. به علاوه، فقط آن تکه از داده‌ها را که توسط کنترل صفحه‌بندی در سمت کاربر نیاز است را بارگذاری می‌کنید. این تکنیک، تکنیکی است که در نمایش نتایج جستجوی گوگل استفاده شده است.

در این آموزش، نحوه ساخت یک کامپوننت صفحه‌بندی سفارشی‌سازی شده با استفاده از React برای صفحه‌بندی دیتاست‌های بزرگ را خواهیم دید. برای این که همه چیز را در حد ممکن ساده نگه داریم، یک View صفحه‌بندی شده راجب کشورها می سازیم، که داده‌های مورد نیاز برای آن را از قبل داریم.

در تصوبر زیر، نمونه‌ای از چیزی که در این آموزش خواهیم ساخت را می‌بینید:

صفحه بندی در react

جدول محتویات:

  1. پیش‌نیاز‌ها
  2. شروع کار
  3. کامپوننت CountryCard
  4. کامپوننت صفحه‌بندی
  5. کامپوننت برنامه
  6. ارتقای پروژه با کمی استایل بندی
  7. نتیجه گیری

پیشنیازها:

قبل از شروع کار،‌ باید مطمئن شوید که Node را بر روی دستگاه خود نصب کرده اید. همچنین از آنجایی که ما از Yarn برای مدیریت پکیج‌ها به جای npm که به همراه Node‌ قرار دارد استفاده خواهیم کرد، پیشنهاد می‌کنم آن را بر روی سیستم خود نصب کنید.

ما با استفاده از پکیج create-react-app، Boilerplate مورد نیاز برای برنامه React خود را خواهیم ساخت. همچنین باید مطمئن شوید که این پکیج به صورت Global بر روی سیستم شما نصب شده است. اگر در حال استفاده از npm‌ نسخه 5.2 به بالا هستید، از آنجایی که می‌توانیم از دستور npx‌ استفاده کنیم، شاید نیازی نباشد که create-reacta-app را به عنوان یک Dependency گلوبال نصب کنید.

در نهایت،‌ در این آموزش فرض می‌شود که شما از قبل با React آشنا هستید.

شروع کار:

یک برنامه جدید بسازید

با استفاده از دستور زیر، یک برنامه جدید React بسازید. می‌توانید نام برنامه خود را هر چه که می‌خواهید قرار دهید.

create-react-app react-pagination

Npm نسخه 5.2 یا بالاتر:

اگر از npm‌ نسخه 5.2 یا بالاتر استفاده می‌کنید، این نسخه یک npx باینری اضافی به همراه خود دارد. با استفاده از باینری npx،‌ نیازی نیست که create-react-app را به صورت global بر روی دستگاه خود نصب کنید. می‌توانید با این دستور ساده یک برنامه جدید را شروع کنید:

npx create-react-app react-pagination

نصب Dependencyها

سپس، Dependency‌هایی که برای برنامه‌مان نیاز داریم را نصب می‌کنیم. برای انجام این کار،‌ دستور زیر را اجرا کنید:

yarn add bootstrap prop-types react-flags countries-api
yarn add -D npm-run-all node-sass-chokidar

ما node-sass-chokidar را به عنوان یک Dependency‌ توسعه نصب کرده‌ایم، تا بتوانیم از SASS استفاده کنیم.

حال شاخه src را باز کنید و پسوند تمام فایل‌های .css را به .scss تغییر دهید. همانطور که پیش می‌رویم، فایل‌‌های .css مورد نیاز توسط node-sass-chokidar کامپایل می‌شوند.

اسکرپتهای npm را تغییر دهید

فایل 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"
}

Bootstrap CSSرا اضافه کنید

از آنجایی که به یک سری استایل بندی‌های پیشفرض نیاز خواهیم داشت، پکیج bootstrap را به عنوان یک Dependency نصب کردیم. همچنین از استایل‌های کامپوننت صفحه‌بندی Bootstrap استفاده خواهیم کرد. برای اضافه کردن Bootstrap به برنامه، فایل src/index.js را ویرایش کنید و این خط را در مقابل هر import اضافه کنید:

import "bootstrap/dist/css/bootstrap.min.css";

راه اندازی آیکونهای Flag

ما react-flags را به عنوان یک Dependency در برنامه‌مان نصب کردیم. برای دسترسی به آیکون‌های flag آن‌ها از برنامه‌مان، باید عکس‌ها را به شاخه public برنامه اضافه کنیم. این دستور را در ترمینال خود اجرا کنید و آیکون‌های flag را کپی کنید:

mkdir -p public/img
cp -R node_modules/react-flags/vendor/flags public/img

اگر در حال استفاده از یک دستگاه با سیستم عامل ویندوز هستید، از این دستور به جای دستور بالایی استفاده کنید:

mkdir \public\img
xcopy \node_modules\react-flags\vendor\flags \public\img /s /e

شاخه components

ما کامپوننت‌های React زیر را برای برنامه‌مان خوهیم ساخت:

  • CountryCard - این کامپوننت به سادگی نام، منطقه و پرچم یک کشور را رندر می‌کند.
  • Pagination - این کامپوننت شامل منطقی برای ساخت،‌ رندر و جابه‌جایی میان صفحات موجود در صفحه‌بندی است.

در داخل شاخه src،‌ شاخه components را بسازید تا بتوانیم تمام کامپوننت‌های خود را در آن قرار دهیم.

برنامه را شروع کنید

با اجرای دستور زیر در yarn، برنامه را اجرا کنید:

yarn start

حال برنامه شروع شده، و فرایند توسعه می‌تواند آغاز شود. دقت کنید که با استفاده از تابع live reloading، یک تب در مرورگر برای شما ایجاد شده تا بتوانید همزمان با توسعه،‌ تغییرات اعمال شده را همگام سازی کنید.

تا به اینجا، ظاهر برنامه باید به این شکل باشد:

صفحه بندی در react

کامپوننت CountryCard:

فایل جدیدی به نام CountryCard.js در شاخه src/components بسازید و قطعه کد زیر را در آن قرار دهید:

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;

کامپوننت CountryCard نیازمند یک Prop به نام country است، که شامل داده‌هایی در مورد کشور می‌شود که در انتظار رندر شدن هستند. همانطور که در propTypes موجود در کامپوننت CountryCard دیدید، أبجکت country باید شامل داده‌های زیر شود:

  • cca2 - کد دو رقمی کشور
  • region - منطقه کشور، مثلا آفریقا
  • name.common - نام کشور، مثلا نیجریه

در زیر،‌ یک نمونه‌ آبجکت کشور را میبینید:

{
  cca2: "NG",
  region: "Africa",
  name: {
    common: "Nigeria"
  }
}

همچنین به نحوه رندر کردن پرچم کشور با استفاده از پکیج react-flags دقت کنید.

کامپوننت Pagination:

فایل جدیدی به نام Pagination.js در شاخه src/components بسازید و قطعه کد زیر را به آن اضافه کنید:

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;

همانطور که در آبجکت propTypes مشخص شده است، کامپوننت Pagination می‌تواند چهار prop را دریافت کند:

  • totalRecords - تعداد رکورد‌هایی که قرار است صفحه‌بندی شوند را مشخص می‌کند. این مورد اجباری است.
  • pageLimit - تعداد رکوردهایی که در هر صفحه باید نمایش داده شوند را مشخص می‌کند. اگر مشخص نشده باشد، همانطور که در بخش constructor() تعریف شده است، مقدار ۳۰ را در خود دارد.
  • pageNeighbours - تعداد صفحات اضافی که در هر سمت صفحه فعلی باید نمایش داده شوند را نشان می‌دهد. کمترین مقدار ۰، و بیشترین مقدار ۲ است. اگر مشخص نشده باشد، همانطور که در بخش constructor() تعریف شده است، به طور پیشفرض مقدار ۰ را در خود دارد. عکس زیر، اثر مقادیر مختلف در pageNeighbours را نشان می‌دهد.
  • صفحه بندی اطلاعات در react
  • onPageChanged - این مورد یک تابع است که زمانی که صفحه فعلی عوض شود، به همراه داده‌های صفحه‌بندی فعلی فراخوانی می‌شود.

در تابع constructor()، به این صورت تعداد تمام صفحات را محاسبه می‌کنیم:

this.totalPages = Math.ceil(this.totalRecords / this.pageLimit);

دقت کنید که در این بخش، از Math.ceil() استفاده می‌کنیم تا یک مقدار integer برای تعداد صفحات دریافت کنیم.

در نهایت، با قرار دادن مقدار ویژگی currentPage به ۰، state مورد نظر را راه‌اندازی می‌کنیم. ما به این ویژگی برای پیگیری صفحه فعلی به صورت داخلی نیاز داریم.

سپس، ادامه داده و متدهایی برای تولید شماره صفحات می‌سازیم. کامپوننت 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);

  }

}

در این بخش، ابتدا دو constant را تعریف می‌کنیم: LEFT_PAGE و RIGHT_PAGE. این constant‌ها برای نمایش نقاطی که می‌خواهیم کنترل‌هایی برای حرکت به سمت چپ و راست داشته باشیم، استفاده می‌شوند.

همچنین یک تابع کمکی به نام range() تعریف می‌کنیم، که به ما کمک می‌کند محدوده‌هایی از اعداد را بسازیم. اگر از سک کتابخانه کاربردی مثل Lodash در پروژه خود استفاده می‌کنید، می‌توانید از تابع _.range() که توسط آن فراهم شده است، استفاده کنید. قطعه کد زیر، تفاوت میان تابع range() که ساختیم و تابع موجود در Lodash را نشان می‌دهد:

range(1, 5); // returns [1, 2, 3, 4, 5]
_.range(1, 5); // returns [1, 2, 3, 4]

سپس، متد fetchPageNumbers() را در کلاس Pagination تعریف می‌کنیم. این متد منطق هسته‌ای برای تولید شماره صفحات که در بخش کنترل صفحه‌بندی نشان داده می‌شوند را مدیریت می‌کند. ما می‌خواهیم صفحه اول و آخر همیشه نمایان باشند.

در ابتدا،‌ چند متغیر تعریف می‌کنیم. متغیر totalnumbers نمایانگر تعداد کل صفحات است که در بخش کنترل نمایش داده می‌شوند. TotalBlocks تعداد کل صفحات نمایان به اضافه دو بلوک اضافه برای راهنماهای چپ و راست را نشان می‌دهد.

اگر totalPage از totalBlocks بزرگتر نباشد، به سادگی یک محدوده اعداد از ۱ تا totalPages را بر می‌گردانیم. در غیر این صورت، یک آرایه از تعداد صفحات، به همراه LEFT_PAGE و RIGHT_PAGE که در آن‌ها صفحات را به چپ و راست منتقل می‌کنیم را بر می‌گردانیم.

دقت کنید که بخش کنترل صفحه‌بندی‌مان تضمین می‌کند که صفحه اول و آخر همیشه نمایان هستند. کنترل‌های چپ و راست نیز در داخل نمایش داده می‌شوند.

حال ادامه داده و متد 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>
    );

  }

}

در اینجا، آرایه تعداد صفحات را با فراخوانی متد fetchPageNumbers() که پیش‌تر ساختیم، تولید می‌کنیم. پس از آن نیز هر صفحه را با استفاده از Array.prototype.map() رندر می‌کنیم. دقت کنید که بر روی هر صفحه رندر شده، رویداد کلیک را ثبت می‌کنیم تا در زمان کلیک فعال شود.

در آخر، متدهای رویدادها را تعریف می‌کنیم. کامپوننت 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);
  }

}

در اینجا، متد gotoPage() را تعریف می‌کنیم، که state را تغییر می‌دهد و مقدار currentPage را صفحه مشخص شده قرار می‌دهد. این متد تضمین می‌کند که آرگومان page یک مقدار حداقل، یعنی ۱، و یک مقدار حداکثر، یعنی تعداد کل صفحات را دارد. در آخر نیز تابع onPageChanged() را به همراه داده‌های state صفحه‌بندی جدید، فراخوانی می‌کند.

وقتی که این کامپوننت اجرا می‌شود، همانطور که در متد handleMoveLefe() نمایش داده شده است، به سادگی و با فراخوانی this.gotoPage(1) به صفحه اول می‌رویم.

به نحوه استفاده از (this.pageNeighbours * 2) در handleMoveLeft() و handleMoveRight() برای انتقال بین صفحات بر اساس شماره صفحه فعلی دقت کنید.

در زیر، نمونه چیزی که ساختیم را می‌بینید:

صفحه بندی اطلاعات react

کامپوننت برنامه:

حال ادامه داده و فایل App.js در شاخه src را تغییر می‌دهیم. فایل 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;

در اینجا، با استفاده از صفات زیر، state کامپوننت App را راه‌اندازی می‌کنیم:

  • allCountries - آرایه‌ای از تمام کشورهای موجود در برنامه‌مان که با یک آرایه خالی ( [ ] ) آغاز شده است.
  • currentCountries - آرایه‌ای از تمام کشورهای نمایش داده شده در صفحه فعلی که با یک آرایه خالی ( [ ] ) آغاز شده است.
  • currentPage - شماره صفحه‌ای که در حال حاضر فعال است، که با مقدار null آغاز شده است.
  • totalPages - تعداد صفحات برای تمام رکوردهای کشورها، که با مقدار null آغاز شده است.

سپس، در متد componentDidMount()، با استفاده از پکیج countries-api و Countries.findAll()، تمام کشورها را دریافت می‌کنیم. سپس state برنامه را بروزرسانی می‌کنیم، و مقدار allCountries را به تمام کشورها تغییر می‌دهیم.

در نهایت، متد onPageChanged() را تعریف کردیم،‌ که هر بار که به صفحه‌ای جدید از صفحه‌بندی منتقل می‌شویم، فراخوانی می‌شود. این متد به ویژگی onPageChanged در کامپوننت Pagination منتقل می‌شود.

در این متد، دو خط وجود دارند که باید به آن‌ها توجه شود. خط اول:

const offset = (currentPage - 1) * pageLimit;

مقدار offset، نقطه شروع برای دریافت رکورد‌ها برای صفحه فعلی را نشان می‌دهد. استفاده از (currentPage - 1) تضمین می‌کند که مقدار offset بر پایه صفر است. برای مثال فرض کنید می‌خواهیم در هر صفحه ۲۵ رکورد را نمایش دهیم و در حال حاضر در حال نمایش صفحه ۵ هستیم. مقدار offset برابر با ((5 - 1) * 25 = 100) خواهد بود.

در واقع، برای مثال اگر در حال گرفتن داده‌‌ها از دیتابیس به صورت عمدی هستید، این کوئری ساده به شما نشان می‌دهد که چگونه می‌توان از offset استفاده کرد:

SELECT * FROM `countries` LIMIT 100, 25

از آنجایی که ما داده‌ها را از دیتابیس به صورت عمدی دریافت نمی‌کنیم، باید راهی برای استخراج داده‌های مورد نیاز برای نمایش در صفحه فعلی پیدا کنیم. این ما را به خط دوم می‌رساند:

const currentCountries = allCountries.slice(offset, offset + pageLimit);

دقت کنید که ما از متد Array.prototype.slice() برای استخراج رکورد های مورد نیاز از allCountries با انتقال offset به عنوان نقطه شروع، و (offset + pageLimit) به عنوان نقطه پایان استفاده می‌کنیم.

دریافت رکوردها در برنامههای واقعی:

در جهت ساده نگه داشتن این آموزش، از هیچ منبع خارجی‌ای رکورد وارد نکردیم. در یک برنامه واقعی، احتمالا از یک دیتابیس یا یک API داده دریافت خواهید کرد. منطق مورد نیاز برای دریافت رکوردها می‌تواند به سادگی به متد onPageChanged() کامپوننت App وارد شود.

فرض کنید یک اندپوینت API فرضی، به نام /api/countries?page={current_page}&limit={page_limit} داریم. قطعه کد زیر، نشان می‌دهد که چگونه می‌توانیم به صورت عمدی کشورها را با استفاده از پکیج axios، از 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 });
   });
}

حال ادامه داده، و کامپوننت App را با اضافه کردن متد render() تمام می‌کنیم. کامپوننت 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>
    );

  }

}

متد render() کاملا ساده است. ما تعداد کل کشورها،‌کشور فعلی، تعداد صفحات، کنترل <Pagination> و سپس <CountryCard> را برای هر کشور در صفحه فعلی رندر می‌کنیم.

دقت کنید که متد onPageChanged() که پیش‌تر تعریف کردیم را به ویژگی onPageChanged در کنترل <Pagination> ارسال کردیم. این مسئله برای دریافت تغییرات صفحه از کامپوننت Pagination بسیار مهم است. همچنین دقت کنید که در هر صفحه ۱۸ کشور را نشان می‌دهیم.

تا به اینجا، برنامه باید چنین ظاهری داشته باشد:

صفحه بندی اطلاعات در react

ارتقای پروژه با کمی استایل بندی:

احتمالا متوجه شدید که به کامپوننت‌هایی که پیش‌تر ساختیم، تعدادی کلاس اضافه کردیم. حال ادامه داده و مقداری قوانین استایل برای آن کلاس‌ها در فایل 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;
    }
  }
}

بعد از اضافه کردن استایل‌ها، برنامه باید چنین ظاهری داشته باشد:

صفحه بندی اطلاعات در react

نتیجه گیری:

در این آموزش، توانستیم یک ویجت صفحه‌بندی سفارشی‌سازی شده در برنامه React خود بسازیم. گرچه، هیچ API‌ای را فراخوانی نکردیم و با هیچ دیتابیس back-endای ارتباط برقرار نکردیم، اما ممکن است برنامه‌مان چنین تعاملاتی را نیاز داشته باشد. شما هیج محدودیتی در روش‌های موجود در این آموزش ندارید و می‌توانید هر گونه که مایلید آن‌ها را گسترش دهید.

منبع

این مطلب را با دیگران به اشتراک بگذارید :

مقالات پیشنهادی

8 دليلي كه نبايد از يك مديريت محتوا استفاده كنيد

من تمايل دارم افرادي رو پيدا كنم كه در قدم اول ميخوان به مشتريان چنين ايده اي رو، ترويج بدن كه اون ها ميتونن سايت خودشون رو "به راحتي به كمك يه واژه پ...

۱۰ کاراکتر طراحی شده با HTML و CSS

من همیشه از اینکه می‌بینم چکارهایی با سی‌اس‌اس می‌ توان انجام داد، شگفت زده می‌شوم. توسعه دهندگان وب می‌دانند که با سی‌اس‌اس می‌توان لایه‌های خارق الع...

با استفاده از Laravel GeoIP موقعیت جغرافیایی کاربر را تعیین کنید

اخیرا نیاز به بررسی موقعیت جغرافیایی کاربر برای تعیین موقعیت محلی آنها داشتم. پکیج torann/geoip ساخته شده توسط Daniel Stainblack بسیار برای انجام این...

با استفاده از AntiModerate، تصاویر پیش‌رونده بهتری بسازید

اسکریپت AntiModerate ممکن است چیز زیادی به نظر نیاید؛ اما یکی از بهترین اسکریپت‌های JS است، که می‌توانید آن را برای پیشرفت کارایی بر روی صفحه‌های بزرگ...