React.jsでページネーションを作る
こんにちわ山崎です。 最近、WEBアプリのJSをjQueryで書きました。 そこそこの量だったのですが、jQueryだとステート管理ができなくて大変な思いをしたので、 今回は勉強も兼ねて、Reactでページネーションを作っていきます。
デモ動画
仕様としては
- 何らかの書類が数枚あってHTML上は全て同一ページに読み込ませて、JSで表示を切り替えたい。
- 1ページ目が表示されている時は、前へと最初へのボタンがdisabledになり、最後のページが表示されている時は次へボタンと最後へのボタンがdisabledになる。
実装手順
環境構築
create-react-app
コマンドを使ってまずアプリケーションの雛形を作ります。(環境構築については省略するので、詳しくは下記公式リンクを参考にしてください ; ;)
https://github.com/facebook/create-react-app
そしてsrcディレクトリのJSの拡張子がjsになっているので、App.jsとIndex.jsの拡張子を.jsxに変更します。
ルートコンポーネントの編集
次に、src/App.jsxを以下のように変更してください
import React from 'react';
import './App.css';
import Paginator from './Paginator';
import PageList from './PageList';
class App extends React.Component {
constructor(props) {
super(props); // 親クラスのコンストラクターを実行
this.state = {
currentPage: 1,
pageList: ['ページ1', 'ページ2', 'ページ3', 'ページ4', 'ページ5']
}
}
handleNext() {
this.setState({currentPage: this.state.currentPage+1})
}
handleLast() {
this.setState({currentPage: this.state.pageList.length})
}
handlePrev() {
this.setState({currentPage: this.state.currentPage-1})
}
handleFirst() {
this.setState({currentPage: 1})
}
render() {
return <div className="App">
<PageList currentPage={this.state.currentPage}
pageList={this.state.pageList}
/>
<Paginator currentPage={this.state.currentPage}
pageList={this.state.pageList}
handleNext={e => this.handleNext(e)}
handlePrev={e => this.handlePrev(e)}
handleFirst={e => this.handleFirst(e)}
handleLast={e => this.handleLast(e)}
/>
</div>
}
}
export default App;
ページのリストを表示するコンポーネントとページネーターのコンポーネントを2種類用意しました。 コンポーネントのステートはcurrentPageが現在のページ番号、PageListがページの配列です。
表示が切り替わるロジックはとてもシンプルでcurrentPage
の値によってli
要素のクラスを切り替えるというものです。
次に、子コンポーネントのPageList.jsxを作っていきます。
PageList.jsxの作成
import React from 'react';
import PropTypes from 'prop-types';
class Paginator extends React.Component {
render() {
let list = [];
this.props.pageList.forEach((page, i) => {
let num = Number(i);
num = num + 1;
list.push(<li key={`li${i}`} className={this.props.currentPage === num && 'is-current'}>{page}</li>);
});
return <ul className="page-list">
{list}
</ul>
}
}
Paginator.propTypes = {
currentPage: PropTypes.number.isRequired,
pageList: PropTypes.array.isRequired
};
export default Paginator;
let list = [];
this.props.pageList.forEach((page, i) => {
let num = Number(i);
num = num + 1;
list.push(<li key={`li${i}`} className={this.props.currentPage === num && 'is-current'}>{page}</li>);
});
なるべく、テンプレート部分にロジックを入れたくないので、liのHTMLは変数に入れて読み込みます。
親コンポーネントからpropsで受け取った、pageList
の配列を順番にループ回して、li要素を出力します。
ただし、表示するli要素にはis-current
というクラスをつけなければいけません。
インデックス番号は0から始まるため、変数num
にi+1をした値を代入します。
そして、変数numに現在のループのli要素が何ページ目なのかという情報が入ったので、
現在のページ番号と現在のループのページ番号をthis.props.currentPage === num
で比較して、trueだったら、
is-current
というクラス名をつけてあげます。currentPage
のデフォルトの値は1にしてあるので現在はページ1が表示されているはずです。
次に、ページネーションのコンポーネントを作成していきます。
import React from 'react';
import PropTypes from 'prop-types';
class Paginator extends React.Component {
isFirstButtonDisabled() {
return this.props.currentPage === 1 ? true : false
}
isLastButtonDisabled() {
return this.props.currentPage === this.props.pageList.length ? true : false
}
render() {
return <div className="paginator">
<button className="fa fa-angle-double-left"
onClick={this.props.handleFirst}
disabled={this.isFirstButtonDisabled()}>
</button>
<button className="fa fa-angle-left"
onClick={this.props.handlePrev}
disabled={this.isFirstButtonDisabled()}>
</button>
<span className="current-page"><span id="current-page-num">{this.props.currentPage}</span> / 5ページ</span>
<button className="fa fa-angle-right"
onClick={this.props.handleNext}
disabled={this.isLastButtonDisabled()}>
</button>
<button className="fa fa-angle-double-right"
onClick={this.props.handleLast}
disabled={this.isLastButtonDisabled()}>
</button>
</div>
}
}
Paginator.propTypes = {
currentPage: PropTypes.number.isRequired,
pageList: PropTypes.array.isRequired,
handleNext: PropTypes.func.isRequired,
handlePrev: PropTypes.func.isRequired,
handleFirst: PropTypes.func.isRequired,
handleLast: PropTypes.func.isRequired
};
export default Paginator;
button要素のonClickで親コンポーネントからPropsで渡した各種メソッドを呼びます。
onClick={this.props.handleFirst}
onClick={this.props.handlePrev}
onClick={this.props.handleNext}
onClick={this.props.handleLast}
どれもPageCountを更新するだけのシンプルなメソッドです。
handleNext() {
this.setState({currentPage: this.state.currentPage+1})
}
handleLast() {
this.setState({currentPage: this.state.pageList.length})
}
handlePrev() {
this.setState({currentPage: this.state.currentPage-1})
}
handleFirst() {
this.setState({currentPage: 1})
}
disabledを判定させるロジックは一行で書きたいので、可読性を保つため、メソッド化しました。
isFirstButtonDisabled() {
return this.props.currentPage === 1 ? true : false; // 現在のページが1ページ目だったら、true
}
isLastButtonDisabled() {
return this.props.currentPage === this.props.pageList.length ? true : false; // 現在のページが最後のページだったら、true
}
<button className="fa fa-angle-double-left"
onClick={this.props.handleFirst}
disabled={this.isFirstButtonDisabled()}><!-- 1ページ目だったらdisabledがtrueになる -->
</button>
また、Reactとは関係ないですが、FontAwsomeを使いたいので、public/index.html
のheadタグの前に下記を追加します。
<script src="https://kit.fontawesome.com/319b7cf246.js" crossorigin="anonymous"></script>
更に、src/App.css
を以下の内容に書き換えたら完成です。
button {
appearance: none;
border: none;
background: none;
}
.page-list li {
display: none;
}
.page-list li.is-current {
display: block;
}
かなり簡単なサンプルで、あまり参考にならないと思いますが、 ロジック的にはタブやスライドショーと似ていて使い回せる部分もあると思うのでよかったらやってみてください。