Vue.jsでカスタムページネーションを作る


Vue.jsには[Vue Pagination 2](https://www.npmjs.com/package/vue-pagination-2)というpagination用のライブラリがありますが、仕組みを理解したいと思い自作しました。今回はそのカスタムページネーションについて記事にします。ソースコードは[GitHub](https://github.com/JunNishimura/CustomVuePagination)にて公開しています。

手っ取り早く実装したい方は上記ライブラリを使うことをお勧めします。時間があり、仕組みから理解したい人は読んでくれたら嬉しいです。

# 完成図
![スクリーンショット (159).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191216/43e3081d-b4e7-beca-81e2-931505329035.png)
手の込んだことはしておらず、ページボタンを押すと表示するアイテム一覧が変わるだけの簡単な実装にしています。

# 対象読者
- 普段pagination実装はライブラリで済ませているけど、今こそ中身を理解したいと言う人
- 既存ライブラリのpaginationに満足しておらず、自作paginationを作りたいと思っている人

# 内容
## 方針
![アートボード 1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191216/38ade17f-b421-1b01-ffcf-a5fe8c759c5a.png)
やっていることは決して難しいことではなく、表示したい部分のインデックスを取得して、それに対応する配列の一部分を取ってくるだけです。ページの変更は、表示したいインデックスを変更するだけで大丈夫です。

それでは実装に移りましょう。

## 実装
紙面の都合上、重要な部分のみ取り上げます。詳細に関しては[GitHub](https://github.com/JunNishimura/CustomVuePagination)より確認して頂ければと思います。

Vue.jsの基本はすでに理解している前提で話を進めますが、難しいことはしていないので基本を押さえている方であれば大丈夫だと思います。

ページネーションコンポーネントには次の2つを渡します。
1. アイテムを格納した配列 (items)
2. 1ページに表示したいアイテム数 (itemNumPerPage)

```App.vue
<template>
    <div class="home">
        <pagination 
            :items="items"
            :itemNumPerPage="10"
        />
    </div>
</template>
```
今回はデモなので渡すアイテム配列は要素に文字列を持つ配列にしています。

続いて、ページネーションコンポーネントについて見ていきます。

```Paginaiton.vue
<template>
    <div class="pagination">
        <div class="item-list">
            <item v-for="(item, idx) in displayItems" :key="idx" :title="item" />
        </div>
        <div class="page-btns">
            <page-button @changePage="changePage" v-for="n in pageNum" 
                :key="n" 
                :pageNumber="n"
                :curPage="curPage"
            />
        </div>
    </div>
</template>

<script>
import Item from '@/components/Item.vue';
import PageButton from '@/components/PageButton.vue';

export default {
    components: {
        Item,
        PageButton
    },
    props: {
        items: Array,
        itemNumPerPage: Number,
    },
    data() {
        return {
            curPage: 1, // curPage starts from 1
            pageNum: 0, // number os pages
        }
    },
    created() {
        this.calcPageNum();
    },
    computed: {
        displayItems() {
            const startIdx = (this.curPage - 1) * this.itemNumPerPage;
            const endIdx   = startIdx + this.itemNumPerPage;
            return this.items.slice(startIdx, endIdx);
        },
    },
    methods: {
        changePage(value) {
            this.curPage = value;
        },
        calcPageNum() {
            this.pageNum =  Math.ceil(this.items.length / this.itemNumPerPage);
        }
    }
}
</script>
```
itemコンポーネントは単に渡した数字を表示しているだけなので省きます。

実際に画面に表示したいアイテム部分列を選んでいるのは算出プロパティdisplayItemsです。

```javascript
displayItems() {
    const startIdx = (this.curPage - 1) * this.itemNumPerPage;
    const endIdx   = startIdx + this.itemNumPerPage;
    return this.items.slice(startIdx, endIdx);
}
```
表示したい配列部分の開始、終了インデックスを計算して、スライス関数を使って部分配列を作成して返しています（現在のページ番号を保存する変数curPageは画面に表示されているページ番号と揃えるために1スタートにしている点に注意）。

ページ数の計算はcalcPageNum関数で行っています。この関数はcreatedで呼び出しています。

```javascript
calcPageNum() {
    this.pageNum =  Math.ceil(this.items.length / this.itemNumPerPage);
}
```
Math.ceil()にしているのは、最終ページの表示アイテム数がitemNumPerPageより小さい場合でも表示できるようにするためです。

続いてPageButtonコンポーネントについて見ます。

```PageButton.vue
<template>
    <div class="page-btn">
        <button @click="btnPressed"
            :class="{active: isCurrentPage}"
        >{{ pageNumber }} </button>
    </div>
</template>

<script>
export default {
    props: {
        pageNumber: Number,
        curPage: Number
    },
    computed: {
        isCurrentPage() {
            return this.pageNumber === this.curPage;
        }
    },
    methods: {
        btnPressed() {
            this.$emit('changePage', this.pageNumber);
        }
    }
}
</script>
```
propsでcurPageを受け取り、同じページ番号のボタンの背景色は変えています。

ボタンが押されたときに親コンポーネントに対してcurPageを変更するように知らせています。親コンポーネントであるpagination.vueではその知らせを受け取ったら、changePage関数を呼び出して新しいページ番号にcurPageを変更しています。

```javascript:pagination.vue
changePage(value) {
    this.curPage = value;
}
```
以上で実装に関しては終わりです。

# まとめ
本記事ではVue.jsを使ったカスタムページネーションの作成について解説しました。
時間ができたときに、一つ前、先のページに進むボタンなどを付け加えたいと思っています。

「もっとこうした方が良いよ」「こういうやり方もあるよ」というアドバイスがあれば、ぜひコメントを書いてくださると助かります。

# 参考資料
[Pagination in Vanilla Javascript](https://www.youtube.com/watch?v=IqYiVHrO2U8)