2021鐵人賽
React
一般的內容網站通常都會有將內容分頁或是動態讀取的功能,例如像是臉書的動態頁面,因為會不斷地有新的動態,也就是說它的資料量是會快速增加的,就很適合使用動態讀取(例如:scroll down)的功能;而像是博客來線上書店,新書發布的頻率就沒有臉書動態這麼高,因此系統可以在使用者進入網站的時候知道有多少資料需要呈現,就比較適合用分頁來做,因為大概可以算出有幾頁。
當然,上面說得只是一種分辨的標準,來判斷要使用動態讀取或是分頁功能,實際上除了資料量在一段時間內是否固定,還有很多其他面向需要考量,以這次的投資dashboard來說,因為我想要讓一頁最多就只有3張圖表,而我也能在使用者一進入網站,系統就知道需要多少頁面,因此我覺得分頁的方式蠻適合的。
下圖表示資料的傳遞方向,原本沒有分頁功能元件,資料直接從Charts進入Card元件渲染出來,有了Pagination分頁元件後,就把它插在Charts與Card中間,做了分頁處理之後,才會顯示出來。
由於我希望讓分頁元件是比較靈活的,因此蒐集了網路上各種分頁元件的寫法,綜合一版是我覺得功能比較符合我使用的,功能包含:可以設定每頁的資料數量、可以設定要顯示的分頁數、可以顯示目前是在哪一頁、可以有上一頁跟下一頁的功能。程式碼如下:
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;
}
在Charts元件中插入分頁元件並設定參數,這邊稍微解釋一下參數代表的意義:
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;
做到這邊會發現無法正常運作,因為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的問題,詳情見下篇。