Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

React trên Rails: Xây Dựng Ứng Dụng Full-Stack Liên Mạch

Các công ty xây dựng toàn bộ phía front-end của ứng dụng thường chọn cùng một framework, như Rails, để xây dựng back-end. Nhiều năm qua, đây đã là tùy chọn tốt và đáng tin cậy nhất.

Ngày nay, hàng tấn thư viện và framework, trong một vũ trụ front-end liên tục tiến hóa, cho phép nhà phát triển chọn các nền tảng khác nhau cho cả back-end và front-end và dễ dàng tích hợp chúng.

React đã trở thành thiên hà của front-end Pangea. Nếu bạn làm việc với Ruby on Rails, có khả năng bạn từng cần chuyển đổi trang Rails mặc định sang mã React (hoặc bất kỳ front framework nào khác). Có lẽ, bạn đơn giản yêu thích tính năng Rails + React và muốn tích hợp sức mạnh của cả hai công nghệ vào một ứng dụng duy nhất.

Và, đó là ổn! Bài viết này nhằm khám phá một ví dụ thực tế: một ứng dụng CRUD quản lý danh sách sản phẩm bia. Nó sẽ được tạo ban đầu với Rails, sau đó một front-end mới làm bằng React sẽ được tích hợp thêm.

React trên Rails: Xây Dựng Ứng Dụng Full-Stack Liên Mạch Ứng dụng CRUD bia.

Trong vài bước, bạn sẽ hiểu các mảnh chính của React, cách Rails ôm lấy nó, và cách bạn có thể bắt đầu tích hợp cả Rails và React vào các dự án tương lai của mình.

Thiết Lập (Setup)

Là điều kiện tiên quyết, bạn cần có môi trường được thiết lập với Ruby (và Rails), Node.js, và Yarn.

Bạn có thể ưu tiên chọn npm để quản lý gói front-end, nhưng chúng ta sẽ gắn bó với Yarn để đơn giản.

Trong thư mục bạn thích, chạy lệnh sau:

rails new crud-rails-react

Điều này sẽ khởi động dự án Rails của chúng ta. Sau đó, mở dự án đã tạo trong IDE của bạn và đi thẳng đến Gemfile.

Chúng ta cần sửa một bug mà lệnh này tạo ra cho gem SQLite. Vì vậy, hãy chắc chắn tìm thấy gem sqlite3 và thay đổi nó thành:

gem 'sqlite3', '~> 1.3.10'

Điều này sẽ ngăn chặn một số lỗi đã biết liên quan đến phiên bản database vì chúng ta sẽ dùng SQLite làm database mặc định cho ví dụ CRUD.

Đừng lo lắng; trong các database khác, vấn đề này sẽ không xảy ra.

Cấu Hình Database

Tôi thường thích xây dựng mọi thứ từ cơ sở đến các lớp trên, vì vậy hãy bắt đầu tạo model database.

Chúng ta chỉ cần một, vì vậy không có tính năng dòng lệnh tốt hơn cho nhiệm vụ này là Rails scaffolding:

rails g scaffold Beer brand:string style:string country:string quantity:integer & rake db:migrate

Model này khá cơ bản, vì vậy thoải mái thêm các thuộc tính và kiểu khác khi bạn tiến triển.

Trong thư mục db/migrate, có một tệp mới với tên kết thúc bằng "_create_beers.rb". Đây là bản ghi Rails tạo cho chúng ta để đại diện cho một bia.

Model, lần lượt, sẽ được tạo dưới thư mục app/models. Để chúng như hiện tại và thêm các dòng mã sau vào tệp db/seeds.rb:

Beer.create(brand: 'Double Stout', style: 'Stout', country: 'England', quantity: 54)
Beer.create(brand: 'Spaten', style: 'Helles', country: 'Germany', quantity: 3)
Beer.create(brand: 'Newcastle', style: 'Brown ale', country: 'UK', quantity: 12)

Tệp này sẽ lưu trữ tải dữ liệu ban đầu cho database khi app khởi động. Để giải phóng chúng vào database, chạy lệnh sau:

rake db:seed

Xong! Bây giờ, bạn có một vài bia trên bàn.

Thiết Lập Webpacker

Webpacker là một trong những static module bundler được sử dụng rộng rãi nhất cho ứng dụng JavaScript. Vì vậy, nó là sự kết hợp hoàn hảo để kết hợp khả năng React vào ứng dụng hiện có.

Rails cũng cung cấp cho chúng ta một bundler Webpacker được điều chỉnh hoàn toàn để quản lý ứng dụng giống JavaScript trong Rails.

Để cài đặt nó, thêm dòng thứ hai vào Gemfile của bạn, như sau:

gem 'webpacker', '~> 4.3.x'

Tuyệt! Đây là gem duy nhất chúng ta sẽ cần thêm cho toàn bộ phát triển app. Điều đó chỉ có thể vì chúng ta ủy thác trách nhiệm front-end cho Yarn, sẽ được thiết lập sau trong bài viết này.

Bây giờ, đã đến lúc cài đặt cập nhật bằng cách chạy các lệnh sau:

bundle install
bundle exec rake webpacker:install
bundle exec rake webpacker:install:react

Lệnh đầu tiên nổi tiếng với hầu hết nhà phát triển Rails. Chúng ta chỉ cài đặt tất cả các phụ thuộc, bao gồm cả Webpacker.

Một khi Webpacker được cài đặt, chúng ta có thể mô phỏng các lệnh của nó thông qua Rake để cài đặt phụ thuộc Webpacker, cũng như các phụ thuộc React.

Đây là bước rất quan trọng vì đây là nơi Webpacker sẽ đảm bảo tất cả phụ thuộc JavaScript của bạn được thiết lập đúng cho môi trường Rails, vì vậy đừng bỏ qua nó hoặc thử chạy chúng trực tiếp như bạn thường làm với npm hoặc Yarn, được không?

Khi các lệnh hoàn tất, một vài thư mục và tệp (như node_modules và package.json) cũng sẽ được tạo ra.

Thiết Lập Front-end

Tất cả các thiết lập chúng ta đã làm cho đến nay đủ để có ứng dụng CRUD hoạt động chỉ với Rails. Nếu bạn khởi động Rails server qua lệnh rails s, đây sẽ là kết quả:

React trên Rails: Xây Dựng Ứng Dụng Full-Stack Liên Mạch CRUD bia tự động tạo bởi Rails.

Tuy nhiên, chúng ta muốn CRUD riêng của mình với React.

Trước tiên, bạn cần chắc chắn rằng tất cả phụ thuộc front-end bắt buộc được cấu hình qua Yarn:

  • React
  • React Router để xử lý điều hướng trong ứng dụng React
  • Ant Design cho các component React sẵn sàng dùng

Ant Design (biết như antd) là một thư viện mã nguồn mở phong phú cho ứng dụng cấp doanh nghiệp. Nó cung cấp một đống component React có thể tùy biến cao, đơn giản hóa rất nhiều việc phát triển ứng dụng web.

Để cài đặt mọi thứ, chạy lệnh sau:

yarn add antd react-router-dom

Chúng ta không cần thêm rõ ràng thư viện react vì react-router-dom sẽ làm điều đó.

Lúc này, khi bạn mở tệp package.json, đây sẽ là nội dung tự động tạo:

{
  "dependencies": {
    "@babel/preset-react": "^7.12.1",
    "@rails/webpacker": "4.3.0",
    "antd": "^4.7.2",
    "babel-plugin-transform-react-remove-prop-types": "^0.4.24",
    "prop-types": "^15.7.2",
    "react": "^17.0.0",
    "react-dom": "^17.0.0",
    "react-router-dom": "^5.2.0"
  },
  "devDependencies": {
    "webpack-dev-server": "^3.11.0"
  }
}

Đây là thiết lập cơ bản nhất cho combo Rails-React. Vì vậy, chúng ta sẵn sàng chuyển sang phần code.

Thích Nghi Phía Rails

Một vài hành động quan trọng phải được giải quyết phía Rails trước khi tiến sang front-end.

Trước hết, chúng ta cần định nghĩa controller sẽ tập trung chuyển hướng trang. Vì chúng ta đang tạo ứng dụng single-page (SPA), chỉ cần một controller, mà đã được tạo: BeersController.

Mở nó dưới thư mục app/controllers và thay đổi nội dung như sau:

class BeersController < ApplicationController
  def index
  end
end

Đừng lo; tất cả code chúng ta đã xóa sẽ được đặt trong controller tiếp theo được tạo.

Chức năng duy nhất của controller này là cung cấp route trực tiếp từ Rails đến React, đó là lý do chúng ta chỉ thiết lập phương thức index.

Để kết nối trực tiếp với nó, hãy mở tệp routes.rb dưới thư mục config và thay đổi nội dung thành:

Rails.application.routes.draw do
  root 'beers#index'
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

Lưu ý config root mới. Vâng, chúng ta đang map endpoint root đến phương thức index của beers.

Bạn cũng cần làm rỗng tệp index.html.erb trong thư mục app/views/beers vì chúng ta không muốn bất kỳ nội dung web Rails nào được render. Đây là một thủ thuật chúng ta có thể dùng để buộc Rails chỉ hiển thị code React.

Beer API

Bây giờ, hãy chuyển sang tạo Beer API. Cấu trúc API sẽ gần như giống hệt BeersController nhưng với một vài thay đổi nhỏ. Để tạo nó, chạy lệnh sau:

rails generate controller api/v1/Beers

Hãy chắc chắn tạo nó sử dụng hệ thống versioning. Điều này sẽ cho phép bạn phát triển API của mình trong tương lai và phân biệt nó với các controller thông thường.

Bây giờ, mở tệp app/controllers/api/v1/beers_controller.rb và thay thế code bằng:

class Api::V1::BeersController < ApplicationController
  before_action :set_beer, only: [:show, :edit, :update, :destroy]
  # GET /beers
  # GET /beers.json
  def index
    @beers = Beer.all.order(brand: :asc)
    render json: @beers
  end

  # GET /beers/1
  # GET /beers/1.json
  def show
    if @beer
      render json: @beer
    else
      render json: @beer.errors
    end
  end

  # GET /beers/new
  def new
    @beer = Beer.new
  end

  # GET /beers/1/edit
  def edit
  end

  # POST /beers
  # POST /beers.json
  def create
    @beer = Beer.new(beer_params)
    if @beer.save
      render json: @beer
    else
      render json: @beer.errors
    end
  end

  # PATCH/PUT /beers/1
  # PATCH/PUT /beers/1.json
  def update
  end

  # DELETE /beers/1
  # DELETE /beers/1.json
  def destroy
    @beer.destroy
    render json: { notice: 'Beer was successfully removed.' }
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_beer
      @beer = Beer.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def beer_params
      params.permit(:brand, :style, :country, :quantity)
    end
end

Hầu hết các operation được tái sử dụng từ controller trước.

Đoạn before_action sẽ lo việc khôi phục đối tượng beer phù hợp theo tham số id trong request. Chỉ các operation được đặt vào mảng sau mệnh đề :only sẽ cần tính năng auto-recover này.

Phần còn lại của các phương thức tương đương với từng operation của CRUD. Chỉ nhớ luôn trả về JSON như response cho request của bạn vì đó là định dạng chúng ta sẽ dùng trong component React.

Cuối cùng, bạn sẽ cần thích nghi lại config/routes.rb để bao gồm route mới tạo. Vì vậy, hãy chắc chắn thay đổi nội dung tệp thành:

Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      get 'beers/index'
      post 'beers/create'
      delete 'beers/:id', to: 'beers#destroy'
    end
  end
  root 'beers#index'
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

Route destroy minh họa cách map đến phương thức destroy nếu nó không được thiết lập rõ ràng trong path.

React Component

React hoạt động thông qua component, đóng vai trò như các khối xây dựng của ứng dụng web. Mỗi component làm một hoặc nhiều nhiệm vụ có ý nghĩa như một ngữ cảnh.

Nói ngắn gọn, một component được tạo thành từ một class hoặc function JavaScript có thể nhận properties như tham số, xử lý logic nghiệp vụ bên trong, và cuối cùng, trả về một component UI chức năng đại diện cho một phần của màn hình.

Lấy đoạn mã sau được trích từ các ví dụ tiếp theo chúng ta sẽ xây dựng:


  
...
Honeybadger ©2020.

Vâng, React component sử dụng ngôn ngữ markup tùy chỉnh gọi là JSX (JavaScript XML) rất giống HTML. Tất cả thẻ HTML đều có sẵn trong tệp JSX. Bạn có thể đọc thêm về JSX ở đây.

Ví dụ trên minh họa cách antd xử lý component layout cấu trúc. Component được tạo từ các component khác và xếp chồng lên nhau, cấu thành một tổng thể. Một số nhận properties (tùy chọn hoặc không), và một số có thể có nội dung bên trong.

Component có thể dựa trên class hoặc function.

Component Dựa Trên Class

Component dựa trên class được tạo như class JavaScript thông thường, như cái được hiển thị dưới:

class Beers extends React.Component {}

Chúng kế thừa từ class React.Component, có vòng đời, và cung cấp phương thức tiện ích để thực thi code trước giai đoạn khởi tạo, render, và hủy.

Tuy nhiên, phương thức quan trọng nhất (và bắt buộc) là render(), được gọi mỗi khi component được cập nhật.

Component Dựa Trên Function (Functional Components)

Component dựa trên function sử dụng arrow function của ES6 và đơn giản hóa React component về mặt cú pháp và độ phức tạp.

Cùng component Beer trên sẽ được biểu diễn trong function như sau:

const Beers = () => 
My Beers
;

Điều này đơn giản hơn nhiều, phải không?

Thiết Lập React

Chúng ta đã làm rỗng trang index của Rails. Bây giờ, chúng ta sẽ cho Rails biết rằng nó cần làm React trở thành front-end mặc định.

Để đạt được điều này, bạn cần thêm dòng mã sau vào thẻ <head> của tệp app/views/layouts/application.html.erb:

<%= javascript_pack_tag 'index' %>

Điều này sẽ thêm JavaScript pack vào header ứng dụng của chúng ta, sẽ khiến tất cả tệp JavaScript, bao gồm cả React, được thực thi trong trang index.

Chúng ta cần chắc chắn rằng tệp index.jsx có cùng tên vì nó được trỏ tại import pack.

Với mục đích này, hãy đổi tên tệp app/javascript/packs/hello_react.jsx tự động tạo thành index.jsx.

Sau đó, thay thế code bằng:

import React from "react";
import { render } from "react-dom";
import App from "../components/App";
document.addEventListener("DOMContentLoaded", () => {
  render(, document.body.appendChild(document.createElement("div")));
});

Đừng nhầm lẫn tệp này với tệp ứng dụng React, vì nó chỉ là tệp sẽ tải toàn bộ hệ thống phân cấp ứng dụng React vào DOM thông qua hàm render của ReactDOM.

Thường thì, mọi ứng dụng React bắt đầu từ tệp index.js tải mọi thứ cần thiết, bao gồm chính React.

Thẻ App map component top của hệ thống phân cấp của chúng ta. Vì vậy, hãy tạo nó là index.jsx dưới thư mục javascript/components (tạo thư mục thủ công nếu chúng chưa tồn tại) và đặt code sau vào:

import React from "react";
import Routes from "../routes/index";
import "antd/dist/antd.css";
export default () => <>{Routes};

Thay vào đó, bạn có thể import tệp CSS antd trong index.jsx. Cách nào cũng sẽ hoạt động.

Danh sách route được đặt dưới thư mục routes. Chúng được trích xuất từ thư viện React Router, làm hầu hết việc nặng nhọc cho chúng ta. Đây là nội dung của nó:

import React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Home from "../components/Home";
export default (
  
    
      
    
  
);

Mỗi route của bạn phải được map trong một thẻ Route khác nhau. Tham số path phải khớp với URI của mỗi endpoint route, trong khi tham số component chỉ ra component mà React Router nên chuyển hướng request.

Nhớ rằng chúng ta sẽ có một route đơn cho SPA của mình. Bạn cũng có thể thêm path khác ở đây nếu bạn muốn map /beers cho việc liệt kê bia, ví dụ, nhưng chúng ta sẽ giữ đơn giản.

Cũng lưu ý rằng chúng ta đang import component Home ở đây, mà chưa tồn tại. Vì vậy, hãy tạo nó là Home.jsx dưới thư mục components. Sau đó, thêm code sau vào:

import { Layout } from "antd";
import React from "react";
import Beers from "./Beers";
import Header from "./Header";
const { Content, Footer } = Layout;
export default () => (
  
    

Beer Catalog

Honeybadger ©2020.
);

Khi nói đến React component, tôi thích xây dựng chúng từ trên xuống dưới. Cách này, bạn có thể có cái nhìn tổng quan về tất cả component cần thiết để làm app hoạt động như một tổng thể.

Component Home hoạt động như một assembler; nó chứa tất cả các mảnh component khác của app, như Layout, Header, Content, và Footer.

Quan trọng là phân biệt đúng đâu là nơi mỗi mảnh này đến. Antd cung cấp một đống component sẵn có, như Layout, Content, và Footer, để cấu thành màn hình của bạn.

Chúng chủ yếu tập trung vào cấu trúc các phần trang, nhưng một số cũng cung cấp style CSS tích hợp sẵn, sẽ làm cho chúng ta có giao diện đẹp hơn.

Component Header

Tệp component Header.jsx, cũng nên được tạo trong thư mục javascript/components, sẽ giữ nội dung header. Nó bao gồm một menu antd đơn giản và một div với logo Honeybadger, như hiển thị dưới:

React trên Rails: Xây Dựng Ứng Dụng Full-Stack Liên Mạch Các mục menu Antd.

Dưới đây, bạn có thể tìm thấy code để đặt vào Header.jsx:

import React from "react";
import { Layout, Menu } from "antd";
const { Header } = Layout;
export default () => (
  
Home Our Services Contact
);

Component Menu của Antd khá đơn giản để dùng nhưng rộng về tùy chọn tùy biến, cho phép chúng ta tạo, ví dụ, navigation drawer, drop-down, group, và subgroup.

Lưu ý rằng chúng ta cung cấp defaultSelectedKeys, một mảng báo cho menu biết mục nào đang active.

Menu của chúng ta sẽ không điều hướng đến đâu; chúng sẽ chiếm màn hình để đáp ứng look-and-feel thôi. Vì vậy, hãy chuyển sang component Beers.

Component Beers

Component này tập trung vào việc liệt kê bia, và các action có sẵn trong bảng, như xóa, phân trang dữ liệu, và tải lại bảng.

React trên Rails: Xây Dựng Ứng Dụng Full-Stack Liên Mạch Biểu diễn trực quan các action & component React.

Nhìn hình ảnh trên. Chúng ta đã chia nhỏ component và action xuống cấp độ thấp hơn, để bạn hiểu tốt hơn những gì sẽ được làm ở đây.

React State

React component được xây dựng với một object state. Object này hoạt động như một store gắn trực tiếp với component đã cho. Mỗi component có object state riêng, và mỗi khi bạn thay đổi state, component được render lại.

Hành động đầu tiên của component Beers là hiển thị danh sách trên bảng. Với mục đích này, chúng ta sẽ cần giữ danh sách này trong một mảng:

state = {
  beers: [],
};

Danh Sách Bia (The Beers Listing)

Để cung cấp mảng này, chúng ta sẽ cần lấy danh sách từ API controller chúng ta tạo trước đó. Xem hàm sẽ lấy nó:

loadBeers = () => {
  const url = "api/v1/beers/index";
  fetch(url)
    .then((data) => {
      if (data.ok) {
        return data.json();
      }
      throw new Error("Network error.");
    })
    .then((data) => {
      data.forEach((beer) => {
        const newEl = {
          key: beer.id,
          id: beer.id,
          brand: beer.brand,
          style: beer.style,
          country: beer.country,
          quantity: beer.quantity,
        };
        this.setState((prevState) => ({
          beers: [...prevState.beers, newEl],
        }));
      });
    })
    .catch((err) => message.error("Error: " + err));
};

Vì sự đơn giản, chúng ta sẽ dùng Fetch API có sẵn trong tất cả trình duyệt hiện đại mỗi khi cần request dữ liệu từ API.

Hàm trên lấy một vài bước để lấy mảng bia từ API:

  1. Nó trước tiên request endpoint /index bất đồng bộ và then kiểm tra nếu trạng thái response bằng OK.
  2. Nếu đúng, chúng ta trả về dữ liệu dạng JSON; ngược lại, ném một Error.
  3. Then, chúng ta lặp qua mảng kết quả để cấu thành object bia của riêng chúng ta và thêm vào mảng beers của state.
  4. Nếu có gì sai trong quá trình, block catch sẽ bắt exception và hiển thị nó như message alert.

Tuyệt, phải không? Đây là gần như các bước giống hệt chúng ta sẽ lấy cho tất cả request khác.

Nhưng, làm sao antd hiển thị dữ liệu trong bảng? Câu hỏi hay! Hãy xem code sau:

columns = [
  {
    title: "Brand",
    dataIndex: "brand",
    key: "brand",
  },
  ...{
    title: "",
    key: "action",
    render: (_text, record) => (
       this.deleteBeer(record.id)} okText="Yes" cancelText="No">
         Delete{" "} 
      
    ),
  },
];

Tôi đã đơn giản hóa nó một chút để dễ hiểu hơn. Đây là mảng đại diện cho khung xương của bảng. Đây là cách bảng antd hoạt động; chúng cần nhận metadata thông tin về cấu trúc bảng (hàng và cột) như một mảng.

Mỗi cột là một object trong mảng, và thứ tự quan trọng ở đây. Thuộc tính title nhận tên cột, trong khi tên dataIndex là cách nó sẽ được biết trong component React, và key là định danh duy nhất của nó.

Đối với hầu hết cột, cấu hình tương tự, trừ cột action. Ở đó, chúng ta cần chỉ định link action để kích hoạt khi user muốn xóa item. Lưu ý rằng chúng ta đang dùng component Popconfirm của antd.

Đây là component rất hay giúp việc nhắc người dùng xác nhận action trước khi nó xảy ra. Hình dưới minh họa cách nó trông:

React trên Rails: Xây Dựng Ứng Dụng Full-Stack Liên Mạch Hộp thoại xác nhận trước khi xóa.

Hành Động Xóa (Delete Action)

Để xóa item, chúng ta sẽ cần thực hiện hai operation chính: lời gọi xóa trên API và tải lại bảng.

Hàm xóa tương tự như fetch đầu tiên chúng ta đã làm:

deleteBeer = (id) => {
  const url = `api/v1/beers/${id}`;
  fetch(url, {
    method: "delete",
  })
    .then((data) => {
      if (data.ok) {
        this.reloadBeers();
        return data.json();
      }
      throw new Error("Network error.");
    })
    .catch((err) => message.error("Error: " + err));
};

Thấy không? điều mới duy nhất ở đây là HTTP method được truyền như tham số thứ hai của phương thức fetch. Thêm vào đó, trong mệnh đề then, chúng ta gọi hàm reloadBeers, sẽ re-fetch tất cả bia từ back-end một lần nữa.

Nội dung của hàm này khá là:

reloadBeers = () => {
  this.setState({ beers: [] });
  this.loadBeers();
};

Chúng ta reset mảng beers của state và gọi lại hàm load.

Component Hoàn Chỉnh (The Final Component)

Cuối cùng, chúng ta cần cấu thành component bằng cách gọi rõ ràng các thẻ antd. Hãy xem nó đi kèm với code component cuối cùng:

import { Table, message, Popconfirm } from "antd";
import React from "react";
import AddBeerModal from "./AddBeerModal";
class Beers extends React.Component {
  columns = [
    { title: "Brand", dataIndex: "brand", key: "brand" },
    { title: "Style", dataIndex: "style", key: "style" },
    { title: "Country", dataIndex: "country", key: "country" },
    { title: "Quantity", dataIndex: "quantity", key: "quantity" },
    {
      title: "",
      key: "action",
      render: (_text, record) => (
         this.deleteBeer(record.id)} okText="Yes" cancelText="No">
           Delete{" "} 
        
      ),
    },
  ];
  state = { beers: [] };
  componentDidMount() {
    this.loadBeers();
  }
  loadBeers = () => {
    const url = "api/v1/beers/index";
    fetch(url)
      .then((data) => {
        if (data.ok) {
          return data.json();
        }
        throw new Error("Network error.");
      })
      .then((data) => {
        data.forEach((beer) => {
          const newEl = {
            key: beer.id,
            id: beer.id,
            brand: beer.brand,
            style: beer.style,
            country: beer.country,
            quantity: beer.quantity,
          };
          this.setState((prevState) => ({
            beers: [...prevState.beers, newEl],
          }));
        });
      })
      .catch((err) => message.error("Error: " + err));
  };
  reloadBeers = () => {
    this.setState({ beers: [] });
    this.loadBeers();
  };
  deleteBeer = (id) => {
    const url = `api/v1/beers/${id}`;
    fetch(url, { method: "delete" })
      .then((data) => {
        if (data.ok) {
          this.reloadBeers();
          return data.json();
        }
        throw new Error("Network error.");
      })
      .catch((err) => message.error("Error: " + err));
  };
  render() {
    return (
      <>
        
      
    );
  }
}
export default Beers;

Bây giờ, bạn có thể thấy mọi thứ cùng nhau. Hàm render sẽ hiển thị hai thẻ chúng ta import ở đó: component Table của antd và AddBeerModal (form modal chúng ta sẽ tạo trong vài phút).

Component table rất phong phú về cách nó cho phép chúng ta tự động phân trang qua kết quả bằng cách thiết lập object pagination. Thuộc tính duy nhất chúng ta thêm ở đây là kích thước mỗi trang (5 kết quả mỗi trang).

Thuộc tính dataSource nhận danh sách bia chúng ta đã gắn từ back-end, và thuộc tính columns nhận metadata chúng ta đã xây dựng.

Component AddBeerModal

Dưới bảng, bạn có thể thấy nút để thêm bia mới. Khi nhấn nút này, nó sẽ mở một modal với form để đăng ký bia mới vào catalog, như bạn có thể thấy dưới:

React trên Rails: Xây Dựng Ứng Dụng Full-Stack Liên Mạch Thêm bia mới vào catalog.

Đây là cách tuyệt vời để khám phá cách antd xử lý form.

Trước hết, hãy chia nhỏ action chúng ta sẽ có trong component này. Lưu ý rằng component bản thân được tạo từ hai component: một Button và một Modal.

Điều này nghĩa là chúng ta sẽ phải map operation liên quan đến cả hai:

  • showModal và handleCancel xử lý việc mở và đóng modal.
  • onFinish được kích hoạt khi chúng ta submit form.

Chúng sẽ chơi với state của component, chỉ lưu trữ toggle modal (tức là nó có hiển thị hay không):

state = {
  visible: false,
};

Để hiển thị hoặc ẩn modal, chúng ta chỉ cần toggle boolean này:

this.setState({ visible: true });

Để gọi Beer API và đăng ký bia mới, chúng ta sẽ cần dùng Fetch API lại:

onFinish = (values) => {
  const url = "api/v1/beers/";
  fetch(url, {
    method: "post",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(values),
  })
    .then((data) => {
      if (data.ok) {
        this.handleCancel();
        return data.json();
      }
      throw new Error("Network error.");
    })
    .then(() => {
      this.props.reloadBeers();
    })
    .catch((err) => console.error("Error: " + err));
};

Đây là lần đầu chúng ta gọi request gửi dữ liệu lên server. Trong trường hợp này, chúng ta cũng cần nói rõ cho API biết loại thông tin nào đang được gửi. Đó là lý do thuộc tính headers phải được thông báo.

Nếu mọi thứ đi tốt, chúng ta chỉ đóng modal và tải lại danh sách bảng.

Bây giờ, hãy xem mọi thứ cùng nhau, cùng với component render:

import { Button, Form, Input, Modal, Select } from "antd";
import React from "react";
const { Option } = Select;
class AddBeerModal extends React.Component {
  formRef = React.createRef();
  state = { visible: false };
  onFinish = (values) => {
    const url = "api/v1/beers/";
    fetch(url, {
      method: "post",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(values),
    })
      .then((data) => {
        if (data.ok) {
          this.handleCancel();
          return data.json();
        }
        throw new Error("Network error.");
      })
      .then(() => {
        this.props.reloadBeers();
      })
      .catch((err) => console.error("Error: " + err));
  };
  showModal = () => {
    this.setState({ visible: true });
  };
  handleCancel = () => {
    this.setState({ visible: false });
  };
  render() {
    return (
      <>
        
        
          
); } } export default AddBeerModal;

Antd cho phép chúng ta chỉ định rule cho từng item của form riêng lẻ. Nếu trường bắt buộc, chỉ cần nói vậy bằng cách cung cấp thuộc tính rules. Bạn có thể tùy biến message nó sẽ hiển thị nếu user submit form mà không điền đúng:

React trên Rails: Xây Dựng Ứng Dụng Full-Stack Liên Mạch Validate đầu vào form.

Nhìn component Select, chuyển đổi combo box. Xem việc tạo component phức tạp dễ dàng như thế nào bằng cách chỉ cung cấp đúng thuộc tính. Ví dụ, nếu bạn muốn select có thể tìm kiếm, chỉ cần đặt thuộc tính showSearch, và xong:

React trên Rails: Xây Dựng Ứng Dụng Full-Stack Liên Mạch Lọc kết quả trong Select.

Antd sẽ tự động lọc option select dựa trên đầu vào của bạn.

Styling (Tạo Kiểu)

Đôi khi, bạn sẽ cần cung cấp một vài CSS styling cho component không cung cấp mặc định (như bảng antd) hoặc tùy biến những cái có sẵn tích hợp sẵn.

Để làm điều này, bạn có thể tạo bao nhiêu tệp CSS tùy thích và tổ chức chúng trong cấu trúc khiến bạn hài lòng. Rails đã tạo tệp application.css, dưới thư mục app/assets/stylesheets. Mở nó và thêm nội dung sau:

.site-layout-content {
  background: #fff;
  padding: 24px;
  min-height: 380px;
}
.logo {
  width: 200px;
  min-height: 31px;
  margin: 16px 24px 16px 0;
  float: left;
  background-image: url(https://www.honeybadger.io/images/navbar_logo.svg?1602785015);
  background-repeat: no-repeat;
}
.table-striped-rows th, .table-striped-rows td {
  border-bottom: 1px solid #dedddd !important;
}
.table-striped-rows tr:nth-child(2n) td {
  background-color: #fbfbfb;
}
.table-striped-rows thead {
  background-color: #f1f1f1;
}

Đó là các quy tắc CSS để làm bảng striped, ví dụ. Thoải mái thêm bao nhiêu style thêm ở đây tùy thích.

Testing (Kiểm Thử)

Trước khi đi đến test, chúng ta cần tắt kiểm tra CSRF token mà Rails tự động cấu hình cho app. Để làm vậy, vào tệp app/controllers/application_controller.rb và thay đổi nó thành:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :null_session
end

Cách này, chúng ta tránh phải validate token mỗi lần thực hiện request.

Tuyệt! Bây giờ, khởi động server qua lệnh rails s, truy cập địa chỉ https://localhost:3000/, và chơi với CRUD.

Kết Luận

Là bài tập về nhà, tôi khuyên bạn nên thử implement chức năng update của CRUD. Bạn có thể thích nghi phương thức edit tại API controller để nhận thông tin bia cập nhật và thực hiện update lên database. Cho view, một modal khác sẽ rất phù hợp để chứa form edit.

Bạn cũng có thể tìm source code cho hướng dẫn này ở đây. Chúc học tập vui vẻ!