Tìm hiểu về ReactJS với Rails

Trong một khoảng thời gian tìm hiểu về ReactJS, hôm nay mình viết một bài viết về chủ đề này. Để tìm hiểu về React là gì? v.v.. thì có rất nhiều bài viết trên blog này rồi, do vậy mình đi thẳng vào áp dụng với Rails.

Tiếp xúc với Rails chắc chúng ta quen thuộc với scaffold để sinh tự động với nhóm chức năng crud.

Bài viết này mình sẽ viết về thực thi nhóm chức năng này sử dụng React trong framework Rails.

Để tiện theo dõi mình đặt tên app là demo-react. App này chỉ có chức năng crud với resourcerecords.

$ rails new demo-react
$ cd demo-react
$ rails g scaffold records title:string content:text
$ rake db:create
$ rake db:migrate

Sơ qua về React

Hiểu một cách đơn gianr, ngắn gọn về react.js như tên gọi của nó đó là một framework về javascript.

React.js mục đích để tạo ra cấu trúc DOM ảo qua các định nghĩa trong các tệp tin javascript.

Render()

ReactJS sử dụng phương thức render để tạo ra cấu trúc DOM ảo trên một vị trí bất kỳ trên DOM thật.

$ ->
    ReactDOM.render(
        React.DOM.div({}, "Hello world!"),
        document.getElementById("start")
    )

Lệnh phía trên sẽ tạo ra một thẻ <div>Hello world!</div> bên trong định danh #start trên DOM thật.

Mình cũng có thể định nghĩa thêm các class hay id cho thẻ trên bằng cách khai báo vào bên trong {} cặp ngoặc này.

Tổng quan lại cú pháp cho phương thức render

ReactDOM.render(what, where)

Ở phần what chúng ta có thể tạo ra bất cứ thứ gì mình muốn với cú pháp tổng quan là

React.DOM.*

* ở đây có thể là thẻ div, a, h2, v.v..

Components

Việc viết các components cho các chức năng mình cần để tiện quan sát code, dễ đọc và sử dụng ở những nơi mà mình muốn một cách dễ dàng và hiệu qủa hơn.

Ví dụ về tạo một component

my_component = React.createClass
  function_name: ->
    # code here
  render: ->
    # code here

Sử dụng component

$ ->
    element = React.createElement(my_component)

    ReactDOM.render(
        element,
        document.getElementById("start")
    )

state

Mỗi một component đều có 1 state. Dùng để quản lý các trạng thái của component.

Các phương thức với state như:

# Thiết lập gía trị khởi tạo ban đầu
getInitialState()

# Thiết lập gía trị mới cho state
setState()

# Gán thay đổi gía trị trong state
replaceState()

Việc thay đổi state sẽ được tự động áp dụng cho render của component.

Lấy ví dụ đơn gianr khi mình tạo mới 1 record, sau khi dùng phương thức replaceState() thì record mới đó sẽ được render ra DOM mà không cần load lại trang.

Props

PropsProperties mà chúng ta đã đưa vào component từ phương thức render.

Các thao tác với object của form, v.v.. đều sử dụng props.

Một số hàm với props

# Khởi tạo một gía trị ban đầu cho props trong component
my_component = React.createClass
  ...
  getDefaultProps: ->
    object: []

  render: ->
    # code here

crud trong demo rails app

Phần trên mình đã đi vào gioi thiệu khái quát một số phần cơ bản, quan trọng về reactjs. Tới phần này mình sẽ đi vào triển khai nhóm chức năng crud.

Để bắt đầu với Reactjs tới thực hiện chức năng như trên mình cần làm từng bước từ việc cài đặt tới viết mã.

Cài đặt ReactJS trong rails app

Sử dụng Gem. Copy dòng sau vào Gemfile

  gem 'react-rails', '~> 1.0'

Chạy bundle install để cài đặt gem trên.

Chạy lệnh sau để require cần thiết cho app của mình.

  rails g react:install

Bản chất của lệnh trên là tạo ra thư mục components trong javascripts và 3 dòng sau vào application.js

  //= require react
  //= require react_ujs
  //= require components

Để sử dụng các addons của thư viện, copy dòng sau vào config/application.rb

  config.react.addons = true

Để debug với react trong development.rb

  config.react.variant = :development

Liệt kê toàn bộ records

Trong controller records như sau:

  # app/controllers/records_controller.rb

  class RecordsController < ApplicationController
    def index
      @records = Record.all
    end
  end

Mình sẽ sử dụng phương thức react_component để render ra toàn bộ records như sau:

  <%# app/views/records/index.html.erb %>

  <%= react_component 'Records', { data: @records } %>

Khi đó truy cập vào localhost:3000/records sẽ thấy như sau:

  <div data-react-class="Records" data-react-props="{...}">
  </div>

Để render ra các records thì cần viết phương thức render như sau: Tạo file javascripts/components/records.coffee với nội dung như sau:

  # 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, 'title'
              React.DOM.th null, 'content'
          React.DOM.tbody null,
            for record in @state.records
              React.createElement Record, key: record.id, record: record

Một số lưu ý là:

Như mình đã nói ở phía đầu bài, các hàm getInitialState()getDefaultProps() để khởi tạo gía trị ban đầu.

@Records đó chính là tên mà react_component đã render ra ở phía trên.

Hàm render trên sẽ tạo ra một thẻ div với class là records và thẻ tiêu đề h2 gồm chữ Records.

Như chúng ta thấy ở trên có hàm React.createElement Record. Đó chính là hàm render ra các record.

Để có được các record thì chúng ta cần viết những gì chúng ta cần render ra, và sẽ viết nó là một component tên là Record như sau:

  # app/assets/javascripts/components/record.js.coffee

  @Record = React.createClass
    render: ->
      React.DOM.tr null,
        React.DOM.td null, @props.record.title
        React.DOM.td null, @props.record.content

Nếu là người đã từng làm với Rails chúng ta có thể hiểu một cách tương tự như sau:

File records.coffee định nghĩa tương tự records.slim

File record.coffee tương tự record.slim

Tạo mới một record

Để tạo mới một record đơn gianr chúng ta cần phải có 1 form để submit các thuộc tính mà mình đã điền vào lên server.

Việc này chúng ta làm hoàn toàn bằng js, bởi đây là reactjs. mình sẽ không tạo form tĩnh bằng html.

Để tạo form chúng ta làm như sau. Tạo file record_form.coffee trong mục javascripts/components/ như sau:

  # app/assets/javascripts/components/record_form.js.coffee

  @RecordForm = React.createClass
    getInitialState: ->
      title: ''
      content: ''

    handleChange: (e) ->
      name = e.target.name
      @setState "#{ name }": e.target.value

    valid: ->
      @state.title && @state.content

    render: ->
      React.DOM.form
        className: 'form-inline'
        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: 'text'
            className: 'form-control'
            placeholder: 'Content'
            name: 'content'
            value: @state.content
            onChange: @handleChange

        React.DOM.button
          type: 'submit'
          className: 'btn btn-primary'
          disabled: [email protected]()
          'Create record'

Như phía trên thì chúng ta có hàm khởi tạo gia' trị ban đầu cho form đều là rỗng. Hàm kiểm tra thay đổi các trường input và hàm kiểm tra đã điền hay chưa? valid.

Đến đây chúng ta đã có 1 form để điền dữ liệu và nút submit. Tuy nhiên chưa thể submit được dữ liệu lên server. Chúng ta cần có hàm xử lý button submit trên.

Tạo phương thức handleSubmit như sau:

  # app/assets/javascripts/components/record_form.js.coffee

  @RecordForm = React.createClass
    ...
    handleSubmit: (e) ->
      e.preventDefault()
      data = JSON.stringify({record: @state})
      $.ajax
        url: "/records"
        type: "POST"
        dataType: "JSON"
        contentType: "application/json"
        processData: false
        data: data
        success: (data) =>
          @props.handleNewRecord data
          @setState @getInitialState()

    render: ->
      React.DOM.form
        className: 'form-inline'
        onSubmit: @handleSubmit
      ...

Hàm trên sẽ thực hiện POST dữ liệu lên server ở dạng JSON. Sau khi thành công thì phương thức setState sẽ set lại gia' trị cho form như ban đầu đồng thời gọi phương thức handleNewRecord

Như mình đã viết ở phần đầu, các phương thức thao tác với state để áp dụng cho phương thức render ra DOM của component. Do vậy sau khi submit data thành công, record đã được tạo ra thì cần hiển thị nó ngay sau đó trên DOM.

Để làm được việc này, chúng ta có phương thức là handleNewRecord, mình sẽ viết phương thức này như sau:

  # 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
      ...

handleNewRecord sẽ gọi phương thức addRecord phía trên.

Hàm addRecord sẽ thực hiện update thêm record đã tạo vào mảng records đã khởi tạo ban đầu bởi @state.records thêm một record mới tạo.

@setState records: records

Dòng trên sẽ thiết đặt gía trị mới cho records. Đó là gía trị sau khi đã thêm mới một record.

Chúng ta cũng có thể sử dụng component của react cho hàm addRecord như sau:

  addRecord: (record) ->
    records = React.addons.update(@state.records, {$push: [record]})
    @setState records: records

Như vậy chúng ta đã hoàn thành xong tính năng tạo mới một bản ghi với reactjs.

Tham khảo tính năng update, delete tại phần tham khảo.

Trên đây là một số kiến thức mình tìm hiểu được về ReactJs. Mình vẫn đang tiếp tục tìm hiểu về tính năng phân trang và kết hợp các associations trong các bản ghi.

Mong nhận được góp ý từ phía bạn đọc. Cảm ơn mọi người!

Tham khảo