React.js - Hướng dẫn dùng react.js với rails
Bài đăng này đã không được cập nhật trong 3 năm
1. Giới thiệu
React.js là một Javascript Framework mới khá là phổ biến được tạo ra bởi Facebook. Trong bài viết chúng ta sẽ thử sử dụng nó để viết một ứng dụng giả lập theo dõi chi phí trong rails như hình
-
Mỗi bản ghi sẽ có tiêu đề, thời gian, số tiền
-
Debit: Tổng các giá trị nhỏ hơn 0
-
Credit: tổng các giá trị lớn hơn 0
-
Balance: Tổng các giá trị của tất cả bản ghi
-
Khi người dùng tạo một bản ghi, nó sẽ được thêm vào bảng
-
Người dùng có thể sửa các bản ghi, và xóa nó bằng các nut edit, delete ứng với mỗi bản ghi
2. Hướng dẫn cài đặt để tạo ứng dụng
Bước đầu tiên, ta sẽ tạo một project trong rails
rails new sample_app
Đối với giao diện người dùng trong project này, ta sử dụng bootstrap gem làm theo hướng dẫn như sau Trong Gemfile: cài đặt 2 gem
gem "bootstrap-sass"
gem "sass-rails", "~> 5.0"
Trong app/assets/stylesheets/custom.css.scss
@import "bootstrap-sprockets";
@import "bootstrap";
Cài đặt gem "react-rails" trong Gemfile để sử dụng thư viện react.js
gem 'react-rails', '~> 1.0'
Sau đó chạy lệnh
rails g react:install
Nó sẽ tạo cho bạn 1 file components.js ở trong thư mục app/assets/javascripts/components/. Sau khi chạy xong lệnh trên, thì trong file application.js xuất hiện 3 dòng
//= require react
//= require react_ujs
//= require components
3. Tạo Resource
Chúng ta sẽ tạo một record resource gồm có date,title và amount
rails g resource Record title date:date amount:float
Chạy cơ sở dữ liệu:
rake db:create db:migrate
Tạo 2 record trong rails console, và không quên lệnh rails s
Record.create title: 'Record 1', date: Date.today, amount: 500
Record.create title: 'Record 2', date: Date.today, amount: -100
4. Tạo danh sách các records
Trong app/controllers/records_controller.rb ta có
class RecordsController < ApplicationController
def index
@records = Record.all
end
end
Trong file apps/views/records/index.html.erb
<%= react_component "Records", { data: @records } %>
file này sẽ là cầu nối giữa Rails app và React Components
Ta có một list các record được viết như sau, trong app/assets/javascripts/components/records.js.coffee
@Records = React.createClass
getInitialState: ->
records: @props.data
getDefaultProps: ->
records: []
render: ->
React.DOM.div
className: 'records'
React.DOM.h2
className: 'title'
'Records'
React.DOM.table
className: 'table table-bordered'
React.DOM.thead null,
React.DOM.tr null,
React.DOM.th null, 'Date'
React.DOM.th null, 'Title'
React.DOM.th null, 'Amount'
React.DOM.tbody null,
for record in @state.records
React.createElement Record, key: record.id, record: record
Chúng ta cần tạo mới Record component để hiện thị cho mỗi Record, trong app/assets/javascripts/components/record.js.coffee
@Record = React.createClass
render: ->
React.DOM.tr null,
React.DOM.td null, @props.record.date
React.DOM.td null, @props.record.title
React.DOM.td null, amountFormat(@props.record.amount)
Thêm "$" vào trước giá trị amount, app/assets/javascripts/utils.js.coffee
@amountFormat = (amount) ->
'$ ' + Number(amount).toLocaleString()
Kết quả:
tham khảo tại https://github.com/vietanhbkaaa/React.js-with-rails/pull/2/files
5. Create Records
Ta cần tạo một method Create trong controller, trong app/controllers/records_controller.rb ta viết
class RecordsController < ApplicationController
...
def create
@record = Record.new(record_params)
if @record.save
render json: @record
else
render json: @record.errors, status: :unprocessable_entity
end
end
private
def record_params
params.require(:record).permit(:title, :amount, :date)
end
end
Sau đó, trong file app/assets/javascripts/components/record_form.js.coffee ta sẽ viết như sau
@RecordForm = React.createClass
getInitialState: ->
title: ''
date: ''
amount: ''
valid: ->
@state.title && @state.date && @state.amount
handleChange: (e) ->
name = e.target.name
@setState "#{ name }": e.target.value
handleSubmit: (e) ->
e.preventDefault()
$.post '', { record: @state }, (data) =>
@props.handleNewRecord data
@setState @getInitialState()
, 'JSON'
render: ->
React.DOM.form
className: 'form-inline'
onSubmit: @handleSubmit
React.DOM.div
className: 'form-group'
React.DOM.input
type: 'text'
className: 'form-control'
placeholder: 'Date'
name: 'date'
value: @state.date
onChange: @handleChange
React.DOM.div
className: 'form-group'
React.DOM.input
type: 'text'
className: 'form-control'
placeholder: 'Title'
name: 'title'
value: @state.title
onChange: @handleChange
React.DOM.div
className: 'form-group'
React.DOM.input
type: 'number'
className: 'form-control'
placeholder: 'Amount'
name: 'amount'
value: @state.amount
onChange: @handleChange
React.DOM.button
type: 'submit'
className: 'btn btn-primary'
disabled: !@valid()
'Create record'
Ta có Khi điền đầy đủ thông tin, button Create Record sẽ được enable
Khi tạo ra một record, để hiển thị record đó vào danh sách các record, ta viết thêm một phương thức addRecord trong app/assets/javascripts/components/records.js.coffee
@Records = React.createClass
...
addRecord: (record) ->
records = @state.records.slice()
records.push record
@setState records: records
render: ->
React.DOM.div
className: 'records'
React.DOM.h2
className: 'title'
'Records'
React.createElement RecordForm, handleNewRecord: @addRecord
React.DOM.hr null
...
thao khảo tại: https://github.com/vietanhbkaaa/React.js-with-rails/pull/3/files
6. Xử lý đối với các chỉ số lượng (Amount)
Để tạo các box như hình với những nhiệm vụ đã được đề cập ở đầu bài viết.
Ta làm như sau:
Trong file # app/assets/javascripts/components/amount_box.js.coffee
@AmountBox = React.createClass
render: ->
React.DOM.div
className: 'col-md-4'
React.DOM.div
className: "panel panel-#{ @props.type }"
React.DOM.div
className: 'panel-heading'
@props.text
React.DOM.div
className: 'panel-body'
amountFormat(@props.amount)
Trong file # app/assets/javascripts/components/records.js.coffee
@Records = React.createClass
...
credits: ->
credits = @state.records.filter (val) -> val.amount >= 0
credits.reduce ((prev, curr) ->
prev + parseFloat(curr.amount)
), 0
debits: ->
debits = @state.records.filter (val) -> val.amount < 0
debits.reduce ((prev, curr) ->
prev + parseFloat(curr.amount)
), 0
balance: ->
@debits() + @credits()
...
phần render của records.js.coffee để hiển thị 3 box trên ta viết
@Records = React.createClass
...
render: ->
React.DOM.div
className: 'records'
React.DOM.h2
className: 'title'
'Records'
React.DOM.div
className: 'row'
React.createElement AmountBox, type: 'success', amount: @credits(), text: 'Credit'
React.createElement AmountBox, type: 'danger', amount: @debits(), text: 'Debit'
React.createElement AmountBox, type: 'info', amount: @balance(), text: 'Balance'
React.createElement RecordForm, handleNewRecord: @addRecord
...
Kết quả tham khảo tại: https://github.com/vietanhbkaaa/React.js-with-rails/pull/4
7. Deleting Records
Trước hết ta tạo một phương thức delete trong controller app/controllers/records_controller.rb
class RecordsController < ApplicationController
...
def destroy
@record = Record.find(params[:id])
@record.destroy
head :no_content
end
...
end
trong mỗi record, ta tạo một button Delete, và method handleDelete
# app/assets/javascripts/components/record.js.coffee
@Record = React.createClass
handleDelete: (e) ->
e.preventDefault()
$.ajax
method: 'DELETE'
url: "/records/#{ @props.record.id }"
dataType: 'JSON'
success: () =>
@props.handleDeleteRecord @props.record
render: ->
React.DOM.tr null,
React.DOM.td null, @props.record.date
React.DOM.td null, @props.record.title
React.DOM.td null, amountFormat(@props.record.amount)
React.DOM.td null,
React.DOM.a
className: 'btn btn-danger'
onClick: @handleDelete
'Delete'
Trong app/assets/javascripts/components/records.js.coffee
@Records = React.createClass
...
deleteRecord: (record) ->
records = @state.records.slice()
index = records.indexOf record
records.splice index, 1
@replaceState records: records
render: ->
...
# almost at the bottom of the render method
React.DOM.table
React.DOM.thead null,
React.DOM.tr null,
React.DOM.th null, 'Date'
React.DOM.th null, 'Title'
React.DOM.th null, 'Amount'
React.DOM.th null, 'Actions'
React.DOM.tbody null,
for record in @state.records
React.createElement Record, key: record.id, record: record, handleDeleteRecord: @deleteRecord
kết quả tham khảo tại https://github.com/vietanhbkaaa/React.js-with-rails/pull/5
8. Editing Records
Trong phần này ta sẽ tối ưu một số đoạn code bằng cách sử dụng đoạn lệnh sau trong config/application.rb
...
module Accounts
class Application < Rails::Application
...
config.react.addons = true
end
end
restart sever, sau đó tối ưu một số đoạn code như sau:
# app/assets/javascripts/components/records.js.coffee
@Records = React.createClass
...
addRecord: (record) ->
records = React.addons.update(@state.records, { $push: [record] })
@setState records: records
deleteRecord: (record) ->
index = @state.records.indexOf record
records = React.addons.update(@state.records, { $splice: [[index, 1]] })
@replaceState records: records
Thêm method update trong controller # app/controllers/records_controller.rb
class RecordsController < ApplicationController
...
def update
@record = Record.find(params[:id])
if @record.update(record_params)
render json: @record
else
render json: @record.errors, status: :unprocessable_entity
end
end
...
end
Trong # app/assets/javascripts/components/record.js.coffee
@Record = React.createClass
...
handleEdit: (e) ->
e.preventDefault()
data =
title: React.findDOMNode(@refs.title).value
date: React.findDOMNode(@refs.date).value
amount: React.findDOMNode(@refs.amount).value
# jQuery doesn't have a $.put shortcut method either
$.ajax
method: 'PUT'
url: "/records/#{ @props.record.id }"
dataType: 'JSON'
data:
record: data
success: (data) =>
@setState edit: false
@props.handleEditRecord @props.record, data
...
Trong # app/assets/javascripts/components/records.js.coffee viết
@Records = React.createClass
...
updateRecord: (record, data) ->
index = @state.records.indexOf record
records = React.addons.update(@state.records, { $splice: [[index, 1, data]] })
@replaceState records: records
...
render: ->
...
# almost at the bottom of the render method
React.DOM.table
React.DOM.thead null,
React.DOM.tr null,
React.DOM.th null, 'Date'
React.DOM.th null, 'Title'
React.DOM.th null, 'Amount'
React.DOM.th null, 'Actions'
React.DOM.tbody null,
for record in @state.records
React.createElement Record, key: record.id, record: record, handleDeleteRecord: @deleteRecord, handleEditRecord: @updateRecord
Kết quả Tham khảo tại: https://github.com/vietanhbkaaa/React.js-with-rails/pull/6
9. Kết luận
Trong bài viết này, chúng ta nắm được những bước cơ bản của sử dụng react.js trong rails. Cách sử dụng các phương thức create, update, destroy của react.js. Đây là một công nghệ đáng để thử.
10. Tài liệu tham khảo
https://www.airpair.com/reactjs/posts/reactjs-a-guide-for-rails-developers
All rights reserved