はじめに
Ruby on Railsでプログラミングを学び始めてから、非同期通信も取り入れ始めました。
非同期通信という言葉の意味を調べてもなかなか頭に入ってこなくて、思考が停止してしまったり、理解にとても時間がかかりました。
なんとなく自分で考えながら記述できるようになってきたので、きちんと非同期通信と向き合い始めた今のうちに、理解した範囲の内容をまとめてみようと思います。
最後に実際に「Rails6.1でいいね機能の非同期化を実装した際の書き方」についても記録しています。
同期通信と非同期通信の違い
まずは、基本の同期通信と非同期通信の違いをまとめてみます。
同期通信とは
同期通信は、ブラウザとサーバーがやり取りをする通信方法。
同期通信ではデータを反映させるために、いったんブラウザを全て更新する必要があります。
ページの表示が全て変わるので、切り替わる瞬間にパッと一瞬、画面が白くなります。
例えば、SNSのいいねボタンやフォローボタンのように、ページ全体はそのままでボタンの表示だけ変えたい、という時に同期通信になっていると使い勝手が悪そう…。
ユーザー側から見ても、いいねを押す度に画面が一瞬白くなってブラウザをリロードされたら、ボタンが押しづらくなりそうです。
さらに、「ブラウザを全て更新する」という特性上、webブラウザからサーバーにリクエストを送った後、レスポンスが戻ってくるのを待つ必要があります。
この間、他の作業はできません。
ページ全体を1から作成して表示しているため、ページの表示変更に時間もかかります。
非同期通信とは
対して非同期通信は、ブラウザに入っているJavaScriptがブラウザの一部としてサーバーとやり取りをする通信方法です。
※厳密に言うと違うのですが、私の今の段階では非同期に対するイメージがふわっとしてしまうので、JavaScriptのAjax技術を使う方法と認識することにしました。
Ajaxとは
JavaScriptで非同期通信を実現する技術のこと。
Ajaxとは「Asynchronous JavaScript + XML」の略で、「JavaScriptとXMLを使って非同期にサーバとの間の通信を行うこと。」を意味します。
きちんと理解するためにはAjaxについて知る必要もありそうですが、調べてみたら内容をかみ砕くのに時間がかかりそうだったのでまた今度にします。
Ajaxを使った非同期通信ではwebプラウザから一部の情報をリクエストするので、それ以外の部分は変わりません。
なので、同期通信のように画面が白くなることもありません。
ページの一部分だけをJavaScriptを使って書き換えるので、サーバーにリクエストを送った後、レスポンスを待つ必要がありません。
先ほどのいいね機能の例で言うと、「いいねボタン」を押すと、他の表示は元のままで、この「いいねボタン」だけを書き換える形になります。
非同期通信の本質は、通信の送信と受信のタイミングをずらせるところにあり、他の処理を並行して行う事ができるという特徴があります。
いいねボタンの場合だけで考えるとあまりピンと来ませんが、Googleマップでマップ上をスクロールした時に徐々に表示が更新される仕様でも非同期通信が使われているのだとか…。
その辺の機能に関しては、追々確認してみたいです。
Rails6.1での非同期通信の実装
ここからは、Railsの学習の際に簡単なパターンを試しながら残したメモを確認しつつ、JavaScriptの技術を使用したいいね機能の非同期化を例として記録していきます。
1.ビューの記述
<% if book.favorited_by?(current_user) %>
<%= link_to book_favorites_path(book), method: :delete, remote: true do %>
♥<%= book.favorites.count %> いいね
<% end %>
<% else %>
<%= link_to book_favorites_path(book), method: :post, remote: true do %>
♡<%= book.favorites.count %> いいね
<% end %>
<% end %>
link_toにremote: trueというオプションを記述することで非同期の状態になり、JavaScript形式でリクエストを送るようになります。
また、form_withを使用する際はlocal: falseのオプションを付けるのですが、バージョンによって仕様が変わるようです。私の環境はRails6.1になります。
※いいねボタンは、jsファイルで内容を置き換えることでボタンの表記を変えるので部分テンプレートにしておく。
ビューの記述では、
同期通信:remote: trueやlocal: falseを付けずにHTML形式でリクエストを送る
非同期通信:remote: trueやlocal: falseを付けてJavaScript形式でリクエストを送る
2.コントローラの記述
さっきのlink_toで記述したパスを元にコントローラへ行きます。
def create
book = Book.find(params[:book_id])
@favorite = current_user.favorites.new(book_id: book.id)
@favorite.save
render 'replace_btn'
end
def destroy
book = Book.find(params[:book_id])
@favorite = current_user.favorites.find_by(book_id: book.id)
@favorite.destroy
render 'replace_btn'
end
いいねボタンのcreateとdestroyに飛ばしたので、このアクション内で飛ばしたいjsファイルの名前を記述してあげる必要があります。
今回の場合、「render 'replace_btn'」にしていて、favoritesフォルダのreplace_btn.js.erbを指定しています。
redirect_toやrenderの指定がない場合は、自動的に[app/views/コントローラ名/アクション名.js.erb]ファイルがレスポンスされます。
今回の場合は、単純にredirect_toやrenderを削除して
[app/views/favorites/create.js.erb]と
[app/views/favorites/destroy.js.erb]の2つのファイルを作成してビューに送る指示を記述しても同じことになります。
create.js.erbとdestroy.js.erbでjsファイルを分ける場合はこんな感じ…
def create
book = Book.find(params[:book_id])
@favorite = current_user.favorites.new(book_id: book.id)
@favorite.save
# render 'replace_btn'
end
def destroy
book = Book.find(params[:book_id])
@favorite = current_user.favorites.find_by(book_id: book.id)
@favorite.destroy
# render 'replace_btn'
end
ただ、今回はcreateもdestroyも同じ記述にしたので、最初の書き方の「render 'replace_btn'」を記載して、jsファイルは1個にまとめてしまうことにしました。
また、コントローラの記述で重要なポイントになってくるのが「@favorite」の部分。
jsファイルにコントローラから情報を渡すためには、ビューに情報を渡すときと同じように@を付けてインスタンス変数にする必要があります。
3.jsファイルの記述
$('.favorite-<%= @favorite.book_id %>').html("<%= j(render 'favorites/btn', book: @favorite.book ) %>")
$('')の中に入ったclassをfavorites/btnのhtml(部分テンプレート)に書き換えるための記述
この@favoriteが、さっきのコントローラのcreateアクション内で定義した@favoriteです。
これでコントローラを通って最新の状態になったインスタンス変数を用いて、部分テンプレートで書き換える、という動作になります。
createアクションから@で情報が渡されないと上手く表示が出来なくなります。
4.ビューの記述
$('')の中に入ったclassは、
<td class=<%= "favorite-" + @book.id.to_s %> >
<%= render "favorites/btn", book: @book %>
</td>
いいねを表示したい場所のビューに、classもしくはidでjsファイルから変更箇所を指定するための記述を追加します。
これで、いいねボタンの部分のみが書き換えられる状態になりました。
おわりに
使いやすさや見栄えといった点でも、非同期通信は重要な要素ということが分かりました。
jsファイルを使用するという点で使い方を理解するのに時間がかかりましたが、まだまだ怪しい部分があるので、必要に応じて取り入れつつ、徐々に理解を深めていこうと思います。