ツイッターのように、
下にスクロールするとツイートが無限に出てくるような実装をしてみたい!
こんな方に対して書きました。
この記事を書いている私(@Shoot58153748)は、
2020年2月現在メガベンチャーの社内スタートアップの部署でエンジニア(1年目)をしてます。
プログラミング未経験からメガベンチャーへの転職を成功させた経験・ノウハウ
Webエンジニアになってから学んだこと
をブログにまとめています。
前回は、Vueでのバリデーションを実装しましたが、
今回は、より実践的なアプリに近づけていくために
無限スクロールの実装を紹介します!
今までのLaravelでのWebアプリ開発記事もまとめていますので、
こちらも参考にしてください!
Github: https://github.com/Shuto-san/laravel-vue-docker
無限スクロールとは?何かを一覧表示する気をつけること
これまで、一覧ページの実装では、
tweetテーブルのレコードを全件取得
↓
blade(テンプレート)の画面に組み込み表示
という風に仮に実装していました。
しかし、この実装には大きな問題があります。
まず、画面を取得したときのみツイートが取得できるということ。
つまり画面遷移を伴わないとツイートが取得できません。
そして何よりも問題は、全件取得しているという点です。
数件なら問題ないですが、
100件、1000件、10,000件、10,000,00件…
増えていった場合、、、
1回取得するのに数秒もかかる状況の中
複数人同時にアクセスしたら、、、
想像に容易いですよね?
おそろしくユーザーに優しくないサイトの出来上がりです。
かつサービスが止まるリスクが多分にあります。
そのため通常のサービスは全件取得などは絶対にNG。
必要になる度に数十件ずつ取得してユーザーに表示するようにしています。
その手法として有名なのが、
「ページネーション」
「無限スクロール」
が挙げられます。
「ページネーション」は、
ヤフーのコメント欄みたいな感じで、
ページ番号をクリックすると次々とアイテムが表示される仕組み。
今回実装する「無限スクロール」は、
ツイッターなどが分かりやすい例です。
下までスクロールしたら、また新たなアイテムを取得して表示。その繰り返しです。
ちなみに「ページネーション」に関しては、
laravelにはページネーションの便利なライブラリが用意されています。
が、今回はvueのライブラリを用いて無限スクロールを実装します。
ページ遷移せずアイテムを取得し続ける必要があるため、
アイテム取得は全てjavascriptで制御します。
つまりテンプレートに埋め込まず、VueのDOM操作でアイテムを表示していきます。
少しずつ実践的になってきました
一応、前回と今回のHTTPリクエストの違いを図示したので参考までにご覧ください!
無限スクロール実装例
最初に今回の実装内容をまとめます。
- blade(画面テンプレート):jsで取得したツイートを表示
- js:サーバー側と通信(無限スクロールのライブラリをしよう)
- Controller:ツイートを下さいというリクエストに対しての処理を記述
- Service:実際にDBと接続してツイートを取得する
まずは、前準備としてVueの無限スクロールのライブラリvue-infinite-loading
をインストールします。
npm install --save vue-infinite-loading
インストール完了後は画面から実装していきます。
※ブログでは最低限必要なコードだけ掲載
画面
ファイル名:resources/views/index.blade.php
<div class="contents">
<div v-for="tweet in tweets" :key="tweet.id">
@{{ tweet.tweet }}
</div>
<infinite-loading @infinite="fetchTweets"></infinite-loading>
</div>
今回のポイントは、bladeの変数埋め込み機能を使わずに、
Vueの変数を表示@{{}}
していることです。
@をつけないとbladeの変数として認識されてしまうので忘れないようにしてください。
そして、v-for
でVueの変数をループで回して取得できたツイートをリアルタイムで表示します。
最後に無限スクロールを実装するためのVueコンポーネント<infinite-loading>
を用いて無限スクロールを実装していきます。@infinite="fetchTweets"
でVueのfetchTweets
という無限スクロールのロジックを記述している関数を呼び出しています。(※この後のJS参照)
JS
ファイル名:resources/js/tweet.js
import InfiniteLoading from 'vue-infinite-loading'; // ライブラリの読み込み
Vue.component('infinite-loading', InfiniteLoading); // コンポーネント化
new Vue({
el: '#tweet',
data: {
page: 0, // ツイートテーブルのOffsetを指定するための変数
tweets: [], // ツイートを格納
},
methods: {
fetchTweets($state) {
let fetchedTweetIdList = this.fetchedTweetIdList(); // すでに取得したツイートのIDリストを取得
axios.get('/tweet', {
params: {
fetchedTweetIdList: JSON.stringify(fetchedTweetIdList),
page: this.page
}
})
.then(response => {
if (response.data.tweets.length) {
this.page++;
response.data.tweets.forEach (value => {
this.tweets.push(value);
});
$state.loaded();
} else {
$state.complete();
}
})
.catch(error => {
console.log(error);
})
},
fetchedTweetIdList() {
let fetchedTweetIdList = [];
for (let i = 0; i < this.tweets.length; i++) {
fetchedTweetIdList.push(this.tweets[i].id);
}
return fetchedTweetIdList;
}
}
});
最初の2行で無限スクロールのライブラリをインポートし、コンポーネントを使えるようにします。
変数としては、
DBからツイートを取得する時どこまで取得したかを確認するための目印page
と
取得したツイートを格納し、画面表示するための配列tweets
を用意しています。fetchTweets
内でサーバ側に通信しにいきます。
リクエストのパラメーターとして、fetchedTweetIdList
とpage
を用意しました。
前者は、一度取得したツイートを再度取得することがないようにするためのツイートIDの配列
後者は、先ほど説明したようにツイート所得位置の目印です。then
以下は、レスポンスのツイート数が1つ以上ある場合は、無限スクロール継続(ぐるぐるマーク表示)
0の場合、無限スクロール終了します。(デフォルトではNo more data :)
と最下部に表示)
それでは、サーバー側の実装に入ります。
Controller
ファイル名:app/Http/Controllers/TweetController.php
public function index()
{
// 仮で画面を返す処理のみにする
return view('tweet.index');
}
public function fetch(Request $request) {
// ツイートIDリストをJSONデコード&デコードエラーバリデーション
$decodedFetchedTweetIdList = json_decode($request->fetchedTweetIdList, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return response()->json(['errorMessage' => json_last_error_msg()],500);
}
// ツイートを取得
$tweets = $this->tweetService->extractShowTweets($decodedFetchedTweetIdList, $request->page);
return response()->json(['tweets' => $tweets], 200);
}
fetch
内の処理は、
受け取った配列をデコード&バリデーション(デコードに失敗した場合、エラーメッセージを添えて返却)
↓
サービスクラスのextractShowTweets
にツイートを取得するようにお願い
↓
取得したツイートをレスポンス
という流れです。
それでは、ツイート取得のロジック部分を記述するサービスクラスの実装です。
Service
ファイル名:app/Http/Controllers/TweetController.php
public function extractShowTweets($fetchedTweetIdList, $page)
{
$limit = 10; // 一度に取得する件数
$offset = $page * $limit; // 現在の取得開始位置
$tweets = Tweet::orderBy('created_at', 'desc')->offset($offset)->take($limit)->get();
if (is_null($tweets)) {
return [];
}
if (is_null($fetchedTweetIdList)) {
return $tweets;
}
$showableTweets = [];
foreach ($tweets as $tweet) {
if (!in_array($tweet->id, $fetchedTweetIdList)) {
$showableTweets[] = $tweet;
}
}
return $showableTweets;
}
$limit
で一度にツイートを取得する件数を設定$offset
で現在の取得位置を設定します。
ツイートが投稿されたのが新しい順にソートし、十件ずつ取得します。
ツイートが取得できない場合は空の配列を返却。
JSで無限スクロール終了の判定が下されるでしょう。
また除外するツイートがない(ページをロードして一番はじめに取得する)場合は、
その時点でツイートを返却します。
除外ツイートを除いたツイートリストを作成し、コントローラーに返却
という処理の流れです。
ページを設定しているので、除外ツイートの処理は必要ないかもしれませんが、
除外ツイート処理がある方が確実です。
以上で実装は終わりですが、routes/web.php
にルーティングの設定(Route::get('/tweet', 'TweetController@fetch');
)するのとnpm run dev
でvueをビルドするのを忘れずに!
まとめ
以上、無限スクロールの実装例を紹介しました。
いかがでしたか?
ちょっとだけサービスっぽくなってきたような気もします。
JSは様々なライブラリが用意されているので、
どういうライブラリがあるかを知るだけでかなり実装の幅が広がります。
ぜひ、無限スクロール試してみてください!
次回は、
いいね機能を解説します!
まずはフロント側。リクエスト制御するスキルが学べます。
それではまた!!
コメント