Rails-ReactJs-Action cable
Chao, Xin chào mọi người. Hôm nay mình xin giới thiệu với các bạn cách sử dụng action cable với reactjs trong Rails. Vô luôn nhá! Ngắn gọn: Action cable hổ trợ những chức năng cần tích hợp thời gian thực trong ứng dụng Rails, ví dụ những chức năng về NOTIFICATION, COMMENT, CHAT BOX... ...
Chao, Xin chào mọi người. Hôm nay mình xin giới thiệu với các bạn cách sử dụng action cable với reactjs trong Rails.
Vô luôn nhá!
Ngắn gọn:
- Action cable hổ trợ những chức năng cần tích hợp thời gian thực trong ứng dụng Rails, ví dụ những chức năng về NOTIFICATION, COMMENT, CHAT BOX...
- React là một công cụ hổ trợ quản lý, thao tác những phần phức tạp phía client. Còn overview vể reactjs, các bạn có thể tham khảo nhanh qua bằng một bài viết khác của mình ở đây nhé
Trong link trên, mình đã hướng dẫn luôn phần install reactjs trong Rails, nên mình xin phép bỏ luôn phần install + config reactjs trong bài viết này nhé
Mình cũng bỏ luôn phần nguyên lý hoạt động của reactjs nha, huynh đệ chịu khó vô link đó nắm xí overview cho nhanh
Chúng ta sẽ chọn ứng dụng CHAT để làm demo nhé
Các bạn vào link này để lấy ứng dụng trước
Ngoài việc tích hợp gem "react-rails" vào ứng dụng thì ta sử dụng thêm gem "sprockets-es6"
Chúng ta check lại xem file app/assets/javascripts/application.js đã có đủ dư lày chưa nhá:
//= require jquery //= require jquery_ujs //= require turbolinks //= require react //= require react_ujs //= require components //= require cable //= require channels //= require_tree .
Install Action cable
Chúng ta sẽ sử dụng ES6 nên sau khi bundle sẽ không có file
app/assets/javascripts/channels/index.coffee
vì đổi thành
app/assets/javascripts/channels/index.es6
Chính trong file này, ta thêm đoạn code nhỏ:
var App = {}; App.cable = Cable.createConsumer('ws://localhost:28080');
Ta xóa luôn file app/assets/javascripts/channels/comments.coffee
Tạo component: CommentList
Trong file app/assets/javascripts/components/comments_list.js.jsx, ta thêm đoạn code:
var CommentList = React.createClass({ getInitialState(){ let message = JSON.parse(this.props.message); return {message: message}; }, render() { let comments = this.state.message.comments.map((comment) => { return this.renderComment(comment); }); return ( <div>{comments}</div> ); }, renderComment(comment) { return ( <article key={comment.id}> <h3>Comment by { comment.user.name }</h3> <p>{ comment.content }</p> </article> ); } });
Đây là component để hiển thị comments.
Tạo message Json builder
Chúng ta sẽ thông qua component để khởi tạo dữ liệu. Trong file app/views/messages/_message.json.jbuilder. sẽ có đoạn code sau:
json.(message, :created_at, :updated_at, :title, :content, :id) json.comments(message.comments) do |comment| json.extract! comment, :id, :content json.user do json.extract! comment.user, :id, :name end end
Đoạn code trên sẽ đẩy dữ liệu dạng JSON vào component CommentList ở trên mình vừa mới tạo.
Tạo trang html.erb để hiển thị dữ liệu từ component
Ta tạo file app/views/comments/_new.html.erb với đoạn code(form)
<%= form_for [message, Comment.new], remote: true do |form| %> <%= form.text_area :content, size: "100x20" %><br> <%= form.submit "Post comment" %> <% end %>
Form này để tạo message, và ta sẽ sử dụng nó.
Vì sẽ sử dụng form vừa tạo nên ta sẽ xóa dòng code sau trong app/views/comments/create.js.erb
$('#comments').append('<%=j render @comment %>');
Chúng ta cần phải hiển thị chi tiết message và render component để hiển thị các comments. Bổ sung thêm đoạn code sau vào file app/views/messages/show.html.erb trước <%= render 'comments/comments', message: @message %>
<%= react_component 'CommentList', message: render(partial: 'messages/message.json', locals: {message: @message}) %>
Túm lại, file show.html.erb sẽ như sau:
<h1><%= @message.title %></h1> <p><%= @message.content %></p> <%= react_component 'CommentList', message: render(partial: 'messages/message.json', locals: {message: @message}) %> <%= render 'comments/new', message: @message %>
Lưu ý rằng, cách chúng ta render CommentList sẽ dựa vào nội dùng trả về từ json builder mà chúng ta đã tạo.
Cài đặt Subscription để lắng nghe Action Cable từ React component
Để lắng nghe các thay đổi ở component, ta cần cài đặt subscription từ Action Cable
Lúc này, CommentList component sẽ được thêm đoạn code sau:
setupSubscription(){ App.comments = App.cable.subscriptions.create("CommentsChannel", { message_id: this.state.message.id, connected: function () { setTimeout(() => this.perform('follow', { message_id: this.message_id}), 1000 ); }, received: function (data) { this.updateCommentList(data.comment); }, updateCommentList: this.updateCommentList }); }
Chúng ta cần tạo thêm một chanel hổ trợ việc lắng nghe các events được gửi xuống từ client
app/channels/comments_channel.rb
Add đoạn code sau vào file mới tạo:
class CommentsChannel < ApplicationCable::Channel def follow(data) stop_all_streams stream_from "messages:#{data['message_id'].to_i}:comments" end def unfollow stop_all_streams end end
Trong React component của chúng ta, sẽ sử dụng App.cable.subscriptions.create để tạo một subscription mới cho việc update, và thông qua channel chúng ta muốn lắng nghe. Nó sẽ chấp thuận các method sau để callback hooks.
- connected Subscription đã được kết nối thành công. Chúng ta sử dụng method perform để gọi tới hành động liên quan, và gửi dữ liệu tới method. perform('follow', {message_id: this.message_id}), 1000), gọi CommentsChannel#follow(data).
- received Chúng ta sẽ nhận thông báo dữ liệu mới từ Rails. Tới đây sẽ sử dụng action để cập nhật Component. Cần phải pass qua updateCommentList: this.updateCommentList, là một method của Component được gọi tới với dữ liệu gửi từ Rails
Hoàn chỉnh Component
CommentList sẽ như này cho nhanh nè:
var CommentList = React.createClass({ getInitialState(){ let message = JSON.parse(this.props.message); return {message: message}; }, render() { let comments = this.state.message.comments.map((comment) => { return this.renderComment(comment); }); return ( <div>{comments}</div> ); }, renderComment(comment) { return ( <article key={comment.id}> <h3>Comment by { comment.user.name } </h3> <p>{ comment.content }</p> </article> ); }, componentDidMount() { this.setupSubscription(); }, updateCommentList(comment) { let message = JSON.parse(comment); this.setState({message: message}); }, setupSubscription() { App.comments = App.cable.subscriptions.create("CommentsChannel", { message_id: this.state.message.id, connected: function () { // Timeout here is needed to make sure Subscription // is setup properly, before we do any actions. setTimeout(() => this.perform('follow', {message_id: this.message_id}), 1000); }, received: function(data) { this.updateCommentList(data.comment); }, updateCommentList: this.updateCommentList }); } });
Broadcast message khi comment mới được tạo
Phần sau cùng là cập nhật mới broadcast sang mesage sang listeners rồi đi tới chanel.
Trong file app/jobs/message_relay_job.rb, ta thêm:
class MessageRelayJob < ApplicationJob def perform(message) comment = MessagesController.render(partial: 'messages/message', locals: {message: message}) ActionCable.server.broadcast "messages:#{message.id}:comments", comment: comment end end
sau đó sẽ gọi từ model Comment như sau:
# app/model/comment.rb [...] after_commit {MessageRelayJob.perform_later(self.message)} [...]
Chúng ta cũng cần phải xóa refernece từ CommentRelayJob từ model Comment, từ after_commit sẽ gọi tới MessageRelayJob
Rồi, xong xuôi, chúc các bạn thành công nhé. Tài liệu mình tham khảo từ đây. Hy vọng mọi người sẽ thành công
Xin chào và hẹn gặp lại =))