React on Rails

React và Rails hiện đang là một xu hướng của Framgia nói riêng và rất nhiều công ty trên thế giới nói chung. Hiện tại có nhiều cách để kết hợp 2 công nghệ lại với nhau. Một cách là phân tách thành 2 phần độc lập với nhau. Chúng ta viết toàn bộ phần client bằng React và gọi các API viết bằng Rails. Một cách khác cũng khá phổ biến là sử dụng gem react-rails. Trong bài viết này, mình muốn giới thiệu các bạn một cách làm khác rất thú vị và cũng đang hot gần đây là sử dụng gem react-on-rails.

Giống như gem react-rails, React on Rails có khả năng render trên server-side với fragment caching, đồng thời cũng tương thích với turbolinks. Không giống với react-rails, gem bị phụ thuộc nặng nề vào sprockets và jquery-ujs, React on Rails sử dụng webpack và không phụ thuộc vào jQuery.

Bắt đầu

  1. Đầu tiên chúng ta cần thêm dòng sau vào trong Gemfile và chạy bundle install
gem "react_on_rails", "~> 6"
  1. Chạy generator để tạo ra một project "Hello World" đơn giản
rails generate react_on_rails:install
  1. Chạy bundle và npm
bundle && npm install
  1. Chạy Rails server
foreman start -f Procfile.dev
  1. Truy cập vào trang localhost:3000/hello_world

Nhúng React Component vào trong Rails Views

  • Chế độ bình thường (React sẽ render ở phía client)
<%= react_component("HelloWorldApp", props: @some_props) %>
  • Chế độ server-side rendering (React component sẽ được render trước thành HTML ở trên server)
<%= react_component("HelloWorldApp", props: @some_props, prerender: true) %>
  • Tham số đầu tiên, component_name, là một string khớp với tên mà bạn dùng để expose một React component toàn cục. Vì vậy, như ở ví dụ trên, nếu bạn có một React component tên là "HelloWorldApp", bạn có thể đăng kí bằng đoạn sau:
import ReactOnRails from 'react-on-rails';
import HelloWorldApp from './HelloWorldApp';
ReactOnRails.register({ HelloWorldApp });

Expose component bằng cách này sẽ giúp React on Rails có thể tham chiếu đến component của bạn từ Rails view. Bạn có thể expose bao nhiêu component tuỳ thích, miễn là tên của chúng không được trùng nhau.

  • Tham số thứ 2, @some_props, có thể là một hash hoặc một JSON string. Đây là một tham số không bắt buộc, truyền dữ liệu vào trong component.
  # Rails View
  <%= react_component("HelloWorldApp", props: { name: "Stranger" }) %>
  // Bên trong React component
  this.props.name // "Stranger"

Nó hoạt động như nào?

Lệnh generate sẽ các file webpack vào trong thư mục client. Foreman được sử dụng để compile code và xuất file kết quả vào app/assets/webpack, nơi sẽ được load bởi sprocket. Những file được tự sinh kia sẽ được add vào trong .gitignore.

Trong Rails View, bạn có thể sử dụng helper react_component cung cấp bởi Rails on Rails. Bạn có thể truyền props trực tiếp vào trong helper đó. Bạn cũng có thể khởi tạo một Redux store với view và controller helper react_store, nhờ thế mà dữ liệu có thể được sử dụng xuyên suốt giữa các component.

Client-side render vs. Server-side render

Trong hầu hết các trường hợp, bạn nên sử dụng chế độ prerender: false (chế độ mặc định) để render React component từ Rails view. Trong một số trường hợp, như khi SEO là một vấn đề quan trọng, hoặc khi người dùng không enable Javascript, bạn có thể bật chế độ server rendering bằng cách truyền prerender: true. Bây giờ server sẽ compile mã Javascript bằng ExecJS và truyền kết quả dưới dạng HTML tới client.

Trong bức hình dưới đây, các bạn thấy 3 phần được React on Rails sinh ra.

  1. Một thẻ div ẩn chứa các thuộc tính của React component. Một javascript function sẽ được gọi sau khi page load và convert tất cả các data để build thành React component.
  2. Div wrapper <div id="HelloWorld-react-component-0"> là nơi React được render ra. Nó bao hàm các thành phần được server-render ra cho các React component.
  3. Ngoài ra còn có thêm một đoạn Javascript để console log tất cả mọi message như việc server render bị lỗi. Những thông báo này có thể được config trên server.

Nếu bạn không dùng tính năng server render (prerender: false) thì sự khác biệt lớn nhất trong đoạn HTML được render ra là nó chỉ chứa div bọc bên ngoài: <div id="HelloWorld-react-component-0"/>

Build bundle

Mỗi lần bạn thay đổi code, bạn cần phải build lại để tạo ra file bundle (phần sẽ được tự động nhúng vào trong application.js). Có một file Procfile.dev của Foreman đã được nhúng vào và quản lí việc code thay đổi. Chúng ta chỉ cần chạy: foreman start -f Procfile.dev. Nếu deploy bằng Heroku, lib/assets.rake sẽ take care việc chạy webpack trong quá trình deploy.

Rails context

Khi bạn dùng một 'generator function' để tạo một React component hoặc bạn sử dụng shared redux store, bạn có 2 params để truyền vào function.

  1. Prop bạn truyền vào view helper của hoặc react_component hoặc redux_store
  2. Thông tin ngữ cảnh của Rails, như là current pathname. Bạn có thể tuỳ chỉnh trong config.

Những thông tin này nên là giống nhau dù ở client side hay server side. Bạn có thể truyền railsContext một cách thủ công như là một prop. Nhưng sử dụng rails_context thì tiện dụng hơn bởi nó được truyền một cách tự động đến tận cả những generator function.

Vì vậy bạn cần đăng kí generator function là MyAppComponent, nó sẽ được gọi như sau:

reactComponent = MyAppComponent(props, railsContext);

và nếu dùng store

reduxStore = MyReduxStore(props, railsContext);

Bạn không cần phải thực hiện các lệnh trên, bởi vì React on Rails đã thực hiện chúng một cách tự động ở cả client side và server side. Thông tin về rails context bao gồm như sau:

  {
    # URL settings
    href: request.original_url,
    location: "#{uri.path}#{uri.query.present? ? "?#{uri.query}": ""}",
    scheme: uri.scheme, # http
    host: uri.host, # foo.com
    port: uri.port,
    pathname: uri.path, # /posts
    search: uri.query, # id=30&limit=5

    # Locale settings
    i18nLocale: I18n.locale,
    i18nDefaultLocale: I18n.default_locale,
    httpAcceptLanguage: request.env["HTTP_ACCEPT_LANGUAGE"],

    # Other
    serverSide: boolean # Are we being called on the server or client? NOTE, if you conditionally
     # render something different on the server than the client, then React will only show the
     # server version!
  }

Kết luận

Như vậy là chúng ta đã nắm được cơ bản cơ chế và cách thức sử dụng gem React on Rails để có thể ứng dụng vào dự án thực tế. Nếu các bạn muốn tìm hiểu thêm về gem này, có thể tham khảo tại: https://github.com/shakacode/react_on_rails