用React刻自己的投资Dashboard Day13 - 制作分页(Pagination)功能

tags: 2021铁人赛 React

一般的内容网站通常都会有将内容分页或是动态读取的功能,例如像是脸书的动态页面,因为会不断地有新的动态,也就是说它的资料量是会快速增加的,就很适合使用动态读取(例如:scroll down)的功能;而像是博客来线上书店,新书发布的频率就没有脸书动态这麽高,因此系统可以在使用者进入网站的时候知道有多少资料需要呈现,就比较适合用分页来做,因为大概可以算出有几页。

当然,上面说得只是一种分辨的标准,来判断要使用动态读取或是分页功能,实际上除了资料量在一段时间内是否固定,还有很多其他面向需要考量,以这次的投资dashboard来说,因为我想要让一页最多就只有3张图表,而我也能在使用者一进入网站,系统就知道需要多少页面,因此我觉得分页的方式蛮适合的。

分页功能规划

下图表示资料的传递方向,原本没有分页功能元件,资料直接从Charts进入Card元件渲染出来,有了Pagination分页元件後,就把它插在Charts与Card中间,做了分页处理之後,才会显示出来。

新增Pagination元件

由於我希望让分页元件是比较灵活的,因此蒐集了网路上各种分页元件的写法,综合一版是我觉得功能比较符合我使用的,功能包含:可以设定每页的资料数量、可以设定要显示的分页数、可以显示目前是在哪一页、可以有上一页跟下一页的功能。程序码如下:

src\components\Selector\Selector.js

import React, { useState } from 'react';
import { Row } from 'react-bootstrap';
import styles from './pagination.module.css';

const Pagination = (props) => {
  // 从props导入的资料,包含每页资料、要套入渲染的元件、显示分页数、每页资料数
  const { data, RenderComponent, pageLimit, dataLimit } = props;
  // 总分页数目
  const pages = Math.ceil(data.length / dataLimit);
  // 用一个state储存目前在哪个页面
  const [currentPage, setCurrentPage] = useState(1);
  // 下一页
  const goToNextPage = () => {
    setCurrentPage((page) => page + 1);
  }
  // 上一页
  const goToPreviousPage = () => {
    setCurrentPage((page) => page - 1);
  }
  // 跳至该页面
  const changePage = (event) => {
    const pageNumber = Number(event.target.textContent);
    setCurrentPage(pageNumber);
  }
  // 载入当页资料
  const getPaginatedData = () => {
    const startIndex = currentPage * dataLimit - dataLimit;
    const endIndex = startIndex + dataLimit;
    return data.slice(startIndex, endIndex);
  };
  // 计算目前分页的数字是哪几个分页
  const getPaginationGroup = () => {
    let start = Math.floor((currentPage - 1) / pageLimit) * pageLimit;
    return new Array(pageLimit).fill().map((_, idx) => start + idx + 1);
  };

  return (
    <div>
      <div className="dataContainer">
        <Row>
          {getPaginatedData().map((d, idx) => (
            <RenderComponent key={idx} data={d} />
          ))}
        </Row>
      </div>
      <div className={styles.pagination}>
        <button
          onClick={goToPreviousPage}
          className={`${styles.prev} ${currentPage === 1 ? styles.disabled : ''}`}
        >
          prev
        </button>
        {getPaginationGroup().map((item, index) => (
          <button
            key={index}
            onClick={changePage}
            className={`${styles.paginationItem} ${currentPage === item ? styles.active : null}`}
          >
            <span>{item}</span>
          </button>
        ))}
        <button
          onClick={goToNextPage}
          className={`${styles.next} ${currentPage === pages || pages === 0 ? styles.disabled : ''}`}
        >
          next
        </button>
      </div>
    </div>
  );
};

export default Pagination;

CSS设定
src\UI\Pagination.module.css

.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
}

.paginationItem {
  background: #fff;
  border: 2px solid #666;
  padding: 10px 15px;
  border-radius: 50%;
  height: 45px;
  width: 45px;
  position: relative;
  margin: 0 5px;
  cursor: pointer;
}

.paginationItem span {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.prev,
.next {
  background: #fff;
  border: none;
  padding: 10px;
  color: blue;
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.4);
  margin: 0 10px;
  cursor: pointer;
}

.paginationItem.active {
  border: 1px solid #888;
  color: #888;
  pointer-events: none;
}

.prev.disabled,
.next.disabled {
  pointer-events: none;
  box-shadow: none;
  color: #999;
}

使用Pagination元件

在Charts元件中插入分页元件并设定参数,这边稍微解释一下参数代表的意义:

  • data代表要渲染出来的资料
  • RenderComponent是要用来呈现资料的元件
  • dataLimit是每页要呈现的资料数,在这边就是只要呈现几个Card元件
  • pageLimit是分页数量设定,也就是在网页最下方的分页区块,一次要显示几个分页,我这边的设定是用资料总数除以dataLimit设定的页数再无条件进位,目的是要让所有分页一次呈现出来。

src\components\Charts\Charts.js

import React from 'react';
import Card from './Card';
import Pagination from '../../UI/Pagination';

const Charts = (props) => {
  return (
    <Pagination
      data={props.charts}
      RenderComponent={Card}
      pageLimit={Math.ceil(props.charts.length / 3)}
      dataLimit={3}
    />
  )
};

export default Charts;

修改Card元件的程序码

做到这边会发现无法正常运作,因为Pagination这个元件给子元件的名称不符合之前的设定:

撷取一小段Paginaiton.js

<div className="dataContainer">
    <Row>
      {getPaginatedData().map((d, idx) => (
        <RenderComponent key={idx} data={d} />
      ))}
    </Row>
</div>

可以发现这边是用data,所以子元件就要用props.data,因为这次用的子元件是Card,就把它拿出来看看,发现原本是写props.item,只要把它都改成props.data即可:

src\components\Charts\Card.js

 import Highcharts from 'highcharts/highstock';
 import HighchartsReact from 'highcharts-react-official';
 import fredAPI from './fredAPI';
+import { Col } from 'react-bootstrap';

 const Card = (props) => {
   const [chartOption, setChartOption] = useState({
     title: {
-      text: props.item.title
+      text: props.data.title
    },
    xAxis: {
      type: "datetime",
      ...
    },
    series: [
       {
-       name: props.item.title
+       name: props.data.title
       }
     ]
   });
   
   ...
   
   useEffect(() => {
-    fetchData(props.item.series_id);
+    fetchData(props.data.series_id);
   }, [fetchData, props]);

   return (
-    <div className={styles.chartFrame}>
-      <HighchartsReact
-        highcharts={Highcharts}
-        constructorType={'stockChart'}
-        options={chartOption}
-      />
-      <div className={styles.chartInfo}>
-        <p className={styles.source}>source: {props.item.source}</p>
-        <p className={styles.date}>updated: {props.item.updated}</p>
+    <Col sm={12} md={12} lg={6} xxl={4} className={styles.chartItem} key={props.data.id}>
+      <div className={styles.chartFrame}>
+        <HighchartsReact
+          highcharts={Highcharts}
+          constructorType={'stockChart'}
+          options={chartOption}
+        />
+        <div className={styles.chartInfo}>
+          <p className={styles.source}>source: {props.data.source}</p>
+          <p className={styles.date}>updated: {props.data.updated}</p>
+        </div>
+        <div>
+          <p className={styles.document}>{props.data.document}</p>
+        </div>
       </div>
-      <div>
-        <p className={styles.document}>{props.item.document}</p>
-      </div>
-    </div>
+    </Col>

小结

到这边大概就完成了,第一次写分页功能,还算是蛮快的,到目前就完成筛选跟分页功能,接下来要来解决网页在不同的分页来回,会重复打API的问题,详情见下篇。


<<:  依赖反转原则 Dependency Inversion Principle

>>:  Day 13 - Spring Boot & JPA

查看fbx文件的version

用文本的方式打开文件,就可以看到版本了: FBXHeaderExtension: { FBXHead...

D12. 学习基础C、C++语言

D12: break跟continue 在回圈里通常要执行完才能离开,这时候break的用意就是为了...

Day 25 [模块化] 前端模块化:CommonJS,AMD,CMD,ES6

文章参考自 https://juejin.im/post/6844903744518389768 h...

【心得】你今天种菜了吗? grid之路-grid的使用(6)

画栏位线条时我们都要计算要画几条线以及每一格的大小,以便接下来完美的将物件填入格子里 像是: gri...

Day21:【技术篇】SSH 的基本运作原理

一、前言   会想了解 SSH 是因为工作上和自己使用 GitHub 时,都有看过这个名词,所以有稍...