はじめに
前回DM機能実装の記事を書きました。コチラ→(https://qiita.com/YukiyaOgura/items/0e4888c6aad8dc1f9bf9)
実際に何日か使用して、あれ?なんかおかしい、、、ということに気づいて、その違和感の原因と改善方法を記載します。
違和感とは
結論から申し上げると、メッセージを送信するたびに再度全メッセージをレンダリングしてました。その結果メッセージの量が増えるたびに動作が非常ーーーーーーに重くなり、送信のたびに画面トップまで戻らされました(毎回全メッセージをビューごとレンダリングする為)
その時のソースコードがこちら。
<div class="chat-message overflow-auto" style="max-height: 750px;">
<% chats.each do |chat| %>
<div id="<%= chat.id %>">
<% if chat.user_id == current_user.id %>
<div class="message self <%= 'no-bg' if chat.image.attached? || chat.video.attached? %>">
<div class="message-body">
<%= chat.message %>
<% if chat.image.attached? %>
<div class='file-container'>
<%= image_tag(chat.image.variant(resize_to_limit: [500, 500]), width: '100%', height: 'auto', class: 'rounded') %>
<%= button_to '保存', saved_files_path(chat_id: chat.id), method: :post, remote: true, class: 'save-btn' %>
<span id="status-<%= chat.id %>" class="status" style="display: none;">保存済み</span>
</div>
<% end %>
<% if chat.video.attached? %>
<div class='file-container'>
<%= video_tag rails_blob_path(chat.video), controls: true, class: 'responsive-video rounded' %>
<%= button_to '保存', saved_files_path(chat_id: chat.id), method: :post, remote: true, class: 'save-btn' %>
<span id="status-<%= chat.id %>" class="status" style="display: none;">保存済み</span>
</div>
<% end %>
</div>
<% if chat.read %>
<span class="read">既読</span>
<% end %>
<p class="chat-timestamp"><%= chat.created_at.strftime("%Y-%m-%d %H:%M")%></p>
</div>
<% else %>
<div class="message other <%= 'no-bg' if chat.image.attached? || chat.video.attached? %>">
<div class="message-sender"><%= chat.user.username %></div>
<div class="message-body">
<%= chat.message %>
<% if chat.image.attached? && chat.user_id != current_user.id %>
<div class="file-container">
<%= image_tag(chat.image.variant(resize_to_limit: [500, 500]), width: '100%', height: 'auto', class: 'rounded') %>
<%= button_to '保存', saved_files_path(chat_id: chat.id), method: :post, remote: true, class: 'save-btn' %>
<span id="status-<%= chat.id %>" class="status" style="display: none;">保存済み</span>
</div>
<% end %>
<% if chat.video.attached? && chat.user_id != current_user.id %>
<div class="file-container">
<%= video_tag rails_blob_path(chat.video), controls: true, class: 'responsive-video rounded' %>
<%= button_to '保存', saved_files_path(chat_id: chat.id), method: :post, remote: true, class: 'save-btn' %>
<span id="status-<%= chat.id %>" class="status" style="display: none;">保存済み</span>
</div>
<% end %>
</div>
<p class="chat-timestamp"><%= chat.created_at.strftime("%Y-%m-%d %H:%M")%></p>
</div>
<% end %>
</div>
$('.chat-message').append("<%= j(render 'newchat', chat: @chat) %>");
$('input[type=text]').val("");
$('.message').html("<%= j(render 'chats/conver', chats: @chats) %>");
この行は、ページ内のclass属性が'message'の要素のHTML内容を更新しています
<%= j(render 'chats/conver', chats: @chats) %>
chats/conver
パーシャルをレンダリングし、その結果をJSON形式にエスケープ(jはescape_javascriptのエイリアス)しています。結果的に、この行は新たに取得したチャットメッセージをページに表示するためのものです。
$('input[type=text]').val("");
この行は、ページ内の全てのテキスト入力フィールド(input[type=text])の値を空文字列にリセットしています。これは、ユーザーがメッセージを送信した後、入力フィールドをクリアするためのものと思われます。
はい。以上が今までのコードです。
改善後のコード
まずは新しく部分テンプレートを作成します。
app/views/chtas/_newchat.html.erb
としましょう。
そしたらchats/_conver.html.erb
のメッセージの表示部分を切り取りましょう。
<div id="<%= chat.id %>">
<% if chat.user_id == current_user.id %>
<div class="message self <%= 'no-bg' if chat.image.attached? || chat.video.attached? %>">
<div class="message-body">
<%= chat.message %>
<% if chat.image.attached? %>
<div class='file-container'>
<%= image_tag(chat.image.variant(resize_to_limit: [500, 500]), width: '100%', height: 'auto', class: 'rounded') %>
<%= button_to '保存', saved_files_path(chat_id: chat.id), method: :post, remote: true, class: 'save-btn' %>
<span id="status-<%= chat.id %>" class="status" style="display: none;">保存済み</span>
</div>
<% end %>
<% if chat.video.attached? %>
<div class='file-container'>
<%= video_tag rails_blob_path(chat.video), controls: true, class: 'responsive-video rounded' %>
<%= button_to '保存', saved_files_path(chat_id: chat.id), method: :post, remote: true, class: 'save-btn' %>
<span id="status-<%= chat.id %>" class="status" style="display: none;">保存済み</span>
</div>
<% end %>
</div>
<% if chat.read %>
<span class="read">既読</span>
<% end %>
<p class="chat-timestamp"><%= chat.created_at.strftime("%Y-%m-%d %H:%M")%></p>
</div>
<% else %>
<div class="message other <%= 'no-bg' if chat.image.attached? || chat.video.attached? %>">
<div class="message-sender"><%= chat.user.username %></div>
<div class="message-body">
<%= chat.message %>
<% if chat.image.attached? && chat.user_id != current_user.id %>
<div class="file-container">
<%= image_tag(chat.image.variant(resize_to_limit: [500, 500]), width: '100%', height: 'auto', class: 'rounded') %>
<%= button_to '保存', saved_files_path(chat_id: chat.id), method: :post, remote: true, class: 'save-btn' %>
<span id="status-<%= chat.id %>" class="status" style="display: none;">保存済み</span>
</div>
<% end %>
<% if chat.video.attached? && chat.user_id != current_user.id %>
<div class="file-container">
<%= video_tag rails_blob_path(chat.video), controls: true, class: 'responsive-video rounded' %>
<%= button_to '保存', saved_files_path(chat_id: chat.id), method: :post, remote: true, class: 'save-btn' %>
<span id="status-<%= chat.id %>" class="status" style="display: none;">保存済み</span>
</div>
<% end %>
</div>
<p class="chat-timestamp"><%= chat.created_at.strftime("%Y-%m-%d %H:%M")%></p>
</div>
<% end %>
</div>
次にchats/_conver.html.erb
にchats/_newchat.html.erb
をレンダリングしましょう。
<div class="chat-message overflow-auto" style="max-height: 750px;">
<% chats.each do |chat| %>
<%= render 'newchat', chat: chat %>
<% end %>
</div>
すっきりしましたね。
では最後にchats/create.js.erb
の記述も変えます。
#.chat-messageクラスを持つエレメントを参照、.appendで選択された内部に新しいHTMLを追加(<%#=%>~~)
$('.chat-message').append("<%= j(render 'newchat', chat: @chat) %>");
#ID:chat_messageを持つformを空にする
$('#chat_message').val("");
#name="chat[image]"を持つフォームを空にする-
$('name="chat[image]"').val("");
#name="chat[video]を持つフォームを空にする"
$('name="chat[video]"').val("");
非同期通信化の特徴として、入力された情報がフォームに残ってしまうというのがあります。逆に同期通信の場合は全てのフォールがクリアになります。
なのでメッセージや画像、動画といったファイルを送信した後、非同期通信の場合は、フォームがそのまま残ってしまうということになりますので必ずこの記述が必須になります。
以上で完了です。
解説
もともと$('.message').html("<%= j(render 'chats/conver', chats: @chats) %>");
というコードがありました。これは、サーバーから受け取った全てのチャット(@chats)
をレンダリングし、それを.message
というクラスを持つHTML要素の内部に挿入するものです。つまり、チャットが追加されるたびに全てのチャットメッセージが再レンダリングされ、表示されていました。
新しいコードは
$('.chat-message').append("<%= j(render 'newchat', chat: @chat) %>");
では、新たに追加されたチャットメッセージ(@chat)
だけがレンダリングされ、それが.chat-message
というクラスを持つHTML要素の末尾に追加されます。つまり、新たなチャットが追加されるたびにそのチャットだけがレンダリングされ、既存のチャットメッセージはそのまま保持されます。これにより、パフォーマンスが向上し、新しいチャットが追加された時に既存のチャットが影響を受けなくなりました。
("<%= j(render 'newchat', chat: @chat) %>");
について
chat: @chat
はコントローラからビューに渡す時に使用される記述です。
ここで@chat
はコントローラで記述した新しく作成されたチャットメッセージのデータが入っているインスタンス変数です。
@chat = Chat.new(room_id: @room.id)#チャットの投稿
これのことですね。
このコードが実行される際、<%= j(render 'newchat', chat: @chat) %>
の部分で、newchat
という名前の部分テンプレートが呼び出され、_newchat.html.erb
ファイルがレンダリングされます。
この部分テンプレートでは、chat: @chat
から渡されたchat
という名前のローカル変数を使用して、新しく作成されたチャットメッセージの内容を表示します。
したがって、chat: @chat
は_newchat.html.erb
という部分テンプレートに@chat
というインスタンス変数をchat
という名前で渡しているということになります。
<div class="chat-message overflow-auto" style="max-height: 750px;">
<% chats.each do |chat| %>
<%= render 'newchat', chat: chat %> #ここのこと
<% end %>
</div>
その結果、chats
配列内の各要素であるchat
ごとに部分テンプレートが呼び出され、それぞれのメッセージが表示されます。これにより、全てのメッセージを一度に再表示するのではなく、個別のメッセージを追加するたびに最新のメッセージのみがビューの一番下に表示されるようになります。
以上ですが、ちょっとした書き方の違いでものすごい差ができることに感動しました。
コメント、いいねお待ちしてます!!