Computer >> Máy Tính >  >> Lập trình >> Ruby

React on Rails:Xây dựng một ứng dụng đơn giản

Các công ty xây dựng toàn bộ mặt trước của các ứng dụng của họ thường chọn cùng một khuôn khổ, chẳng hạn như Rails, để xây dựng mặt sau. Trong nhiều năm, đây là lựa chọn tốt nhất và đáng tin cậy nhất.

Ngày nay, rất nhiều thư viện và khuôn khổ, trong vũ trụ front-end không ngừng phát triển, cho phép các 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 người khổng lồ của Pangea giao diện người dùng. Nếu bạn làm việc với Ruby on Rails, rất có thể bạn đã từng cần chuyển các trang Rails mặc định sang mã React (hoặc bất kỳ khung công tác phía trước nào khác). Có lẽ, bạn chỉ đơn giản yêu thích các tính năng của 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à, điều đó không sao! Bài viết này nhằm mục đích khám phá một ví dụ trong thế giới thực:ứng dụng CRUD quản lý kho sản phẩm bia. Ban đầu nó sẽ được tạo bằng Rails, sau đó một giao diện người dùng mới được tạo bằng React với được tích hợp thêm.

React on Rails:Xây dựng một ứng dụng đơn giản Ứng dụng bia CRUD.

Trong một vài bước, bạn sẽ hiểu các phần chính của React, cách Rails tiếp nhận 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 trong tương lai của mình.

Thiết lập

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

Tốt nhất bạn có thể chọn npm để quản lý các gói phía trước, nhưng chúng tôi sẽ sử dụng Yarn để đơn giản hóa.

Trong thư mục tùy chọn của bạn, hãy chạy lệnh sau:

rails new crud-rails-react

Điều này sẽ bắt đầu dự án Rails của chúng tôi. Sau đó, mở dự án đã tạo trong IDE của bạn và truy cập trực tiếp vào Gemfile .

Chúng tôi cần sửa một lỗi mà lệnh này tạo ra cho đá quý SQLite. Vì vậy, hãy đảm bảo định vị sqlite3 và thay đổi nó thành như sau:

gem 'sqlite3', '~> 1.3.10'

Điều này sẽ ngăn ngừa một số lỗi đã biết liên quan đến lập phiên bản cơ sở dữ liệu vì chúng tôi sẽ sử dụng SQLite làm cơ sở dữ liệu mặc định cho ví dụ CRUD.

Tuy nhiên, đừng lo lắng; trong các cơ sở dữ liệu khác, vấn đề này sẽ không xảy ra.

Cấu hình cơ sở dữ liệu

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

Chúng tôi sẽ chỉ cần một cái, vì vậy không có tính năng lệnh nào tốt hơn cho tác vụ ngoài giàn giáo Rails:

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

Mô hình này khá cơ bản, vì vậy hãy thoải mái thêm các thuộc tính và loại khác khi bạn tiếp tục.

Trong db / migrate thư mục, có một tệp mới có tên kết thúc bằng “_ create_beers.rb ”. Đây là kỷ lục mà Rails tạo ra để chúng tôi đại diện cho một loại bia.

Đến lượt nó, mô hình sẽ được tạo trong ứng dụng / mô hình thư mục. Để nguyên chúng và thêm các dòng mã sau vào db / seed.rb tệp:

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 cơ sở dữ liệu khi ứng dụng khởi động. Để giải phóng chúng vào cơ sở dữ liệu, hãy chạy lệnh sau:

rake db:seed

Đó là nó! Bây giờ, bạn có một số bia trên bàn của bạn .

Thiết lập Webpacker

Webpacker là một trong những gói mô-đun tĩnh được sử dụng rộng rãi nhất cho các ứng dụng JavaScript. Do đó, đây là sự kết hợp hoàn hảo để kết hợp các khả năng của React vào một ứng dụng hiện có.

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

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

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

Tuyệt quá! Đây là viên ngọc duy nhất mà chúng tôi cần thêm để phát triển toàn bộ ứng dụng. Điều đó chỉ có thể thực hiện được vì chúng tôi đang giao trách nhiệm chính cho Yarn, trách nhiệm này sẽ được nêu ở phần sau của bài viết này.

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

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

Cái đầu tiên được hầu hết các nhà phát triển Rails biết đến. Chúng tôi chỉ đang cài đặt tất cả các phần phụ thuộc, bao gồm cả chính Webpacker.

Sau 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 các phần phụ thuộc của Webpacker, cũng như các phần mềm React.

Đây là bước rất quan trọng vì đây là nơi Webpacker sẽ đảm bảo tất cả các phụ thuộc JavaScript của bạn được đặt đúng cách thành môi trường Rails, vì vậy đừng bỏ qua 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 chứ?

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

Thiết lập giao diện người dùng

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

React on Rails:Xây dựng một ứng dụng đơn giản CRUD bia tự động tạo đường ray.

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

Trước tiên, bạn cần đảm bảo rằng tất cả các phụ thuộc giao diện người dùng bắt buộc đều được định cấu hình thông qua Yarn:

  • Phản ứng
  • Bộ định tuyến phản ứng để giải quyết việc điều hướng trong ứng dụng React
  • Thiết kế Ant cho các thành phần React sẵn sàng sử dụng

Ant Design (được gọi là antd) là một thư viện mã nguồn mở phong phú dành cho các ứng dụng cấp doanh nghiệp. Nó cung cấp một loạt các thành phần React có thể tùy chỉnh cao giúp đơn giản hóa rất nhiều việc phát triển các ứng dụng web.

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

yarn add antd react-router-dom

Chúng tôi không cần thêm react một cách rõ ràng thư viện kể từ react-router-dom sẽ làm được.

Tại thời điểm này, khi bạn mở package.json , đây sẽ là nội dung được tạo tự động:

{
  "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"
  }
}

Đó là cách thiết lập cơ bản nhất cho combo Rails-React. Vì vậy, chúng ta nên chuyển sang mã.

Điều chỉnh phía Rails

Một số hành động quan trọng phải được giải quyết ở phía Rails trước khi tiếp tục phía trước.

Trước tiên, chúng ta cần xác định bộ điều khiển sẽ tập trung chuyển hướng trang. Vì chúng tôi đang tạo ứng dụng một trang (SPA), chỉ cần một bộ điều khiển, bộ điều khiển này đã được tạo:BeersController .

Mở nó trong ứng dụng / bộ điều khiển và thay đổi nội dung của nó như sau:

class BeersController < ApplicationController
  def index
  end
end

Đừng lo; tất cả mã chúng tôi đã xóa sẽ được đặt trong bộ điều khiển tiếp theo sẽ được tạo.

Chức năng duy nhất của bộ điều khiển này là cung cấp một tuyến đường trực tiếp từ Rails đến React, đó là lý do tại sao chúng tôi chỉ đặt index phương pháp.

Để kết nối trực tiếp với nó, hãy mở route.rb tệp trong cấu hình và thay đổi nội dung của nó thành như sau:

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 ý root mới cấu hình. Có, chúng tôi đang ánh xạ điểm cuối của gốc tới beers phương pháp chỉ mục.

Bạn cũng sẽ cần làm trống index.html.erb tệp trong ứng dụng / lượt xem / bia ​​ vì chúng tôi không muốn bất kỳ nội dung web Rails nào được hiển thị. Đó là một thủ thuật mà chúng tôi có thể sử dụng để buộc Rails chỉ hiển thị mã React.

API bia

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

rails generate controller api/v1/Beers

Đảm bảo tạo nó bằng hệ thống lập phiên bản. Đ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 bộ điều khiển thông thường.

Bây giờ, hãy mở app / controllers / api / v1 / beers_controller.rb và thay thế mã bằng mã sau:

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 hoạt động được tái chế từ bộ điều khiển trước đó.

before_action đoạn mã sẽ xử lý việc khôi phục đối tượng bia thích hợp theo id tham số trong các yêu cầu. Chỉ các phép toán được đặt vào mảng sau :only mệnh đề sẽ cần tính năng tự động khôi phục này.

Phần còn lại của các phương pháp tương đương với từng hoạt động của CRUD. Chỉ cần nhớ luôn trả về JSON dưới dạng phản hồi cho các yêu cầu của bạn vì đó là định dạng chúng tôi sẽ sử dụng trong các thành phần React của mình.

Cuối cùng, bạn sẽ cần điều chỉnh config / route.rb một lần nữa để bao gồm các tuyến đường mới được tạo. Vì vậy, hãy đảm bảo thay đổi nội dung tệp thành như sau:

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

Đường tiêu diệt trình bày cách ánh xạ tới destroy nếu nó không được đặt rõ ràng trong đường dẫn.

Thành phần React

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

Nói tóm lại, một thành phần được tạo từ một lớp hoặc hàm JavaScript có thể nhận thuộc tính làm tham số, xử lý logic nghiệp vụ bên trong nó và cuối cùng, trả về một thành phần giao diện người dùng 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 xuất từ ​​các ví dụ tiếp theo mà chúng tôi sẽ tạo:

<Layout>
  <Header />
  <Content>...</Content>
  <Footer>Honeybadger ©2020.</Footer>
</Layout>

Có, các thành phần React sử dụng ngôn ngữ đánh dấu tùy chỉnh được gọi là JSX (JavaScript XML) gần giống với HTML. Tất cả các thẻ HTML cũng có sẵn trong các tệp JSX. Bạn có thể đọc thêm về JSX tại đây.

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

Các thành phần có thể dựa trên lớp hoặc dựa trên chức năng.

Thành phần Lớp

Các thành phần dựa trên lớp được tạo như các lớp JavaScript thông thường, như lớp được hiển thị bên dưới:

class Beers extends React.Component {}

Chúng kế thừa từ React.Component lớp, có vòng đời và cung cấp các phương thức sử dụng để thực thi mã trước các giai đoạn khởi tạo, hiển thị 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 thành phần được cập nhật.

Các thành phần chức năng

Các thành phần chức năng sử dụng hàm mũi tên của ES6 và đơn giản hóa các thành phần React về mặt cú pháp và độ phức tạp.

Cùng một Beer thành phần trên sẽ được biểu diễn trong một hàm như sau:

const Beers = () => <div>My Beers</div>;

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

Thiết lập React

Chúng tôi đã làm trống trang chỉ mục Rails. Bây giờ, chúng tôi sẽ cho Rails biết rằng nó cần đặt React trở thành giao diện người dùng mặc định của nó.

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

<%= javascript_pack_tag 'index' %>

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

Chúng tôi cần đảm bảo rằng index.jsx tệp có cùng tên vì nó được trỏ vào gói nhập.

Vì mục đích này, hãy đổi tên app / javascript / pack / hello_react.jsx được tạo tự động tệp vào index.jsx .

Sau đó, thay thế mã bằng mã sau:

import React from "react";
import { render } from "react-dom";
import App from "../components/App";

document.addEventListener("DOMContentLoaded", () => {
  render(<App />, document.body.appendChild(document.createElement("div")));
});

Đừng nhầm 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 render của ReactDOM chức năng.

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

Ứng dụng App gắn thẻ ánh xạ thành phần hàng đầu của hệ thống phân cấp của chúng tôi. Vì vậy, hãy tạo nó dưới dạng index.jsx trong javascript / thành phần thư mục (tạo các thư mục theo cách thủ công trong trường hợp chúng chưa tồn tại) và đặt mã sau vào đó:

import React from "react";
import Routes from "../routes/index";
import "antd/dist/antd.css";

export default () => <>{Routes}</>;

Ngoài ra, bạn có thể nhập tệp CSS antd trong index.jsx . Một trong hai cách tiếp cận sẽ hiệu quả.

Danh sách các tuyến đường được đặt dưới các tuyến đường thư mục. Chúng được trích xuất từ ​​thư viện React Router, thư viện này thực hiện hầu hết các công việc khó khăn cho chúng tôi. Đâ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 (
  <Router>
    <Switch>
      <Route path="/" exact component={Home} />
    </Switch>
  </Router>
);

Mỗi tuyến đường của bạn phải được lập bản đồ trong một Route khác nhau nhãn. path tham số phải khớp với URI của mỗi điểm cuối tuyến, trong khi thành phần App param cho biết thành phần mà Bộ định tuyến React sẽ chuyển hướng yêu cầu.

Nhớ lại rằng chúng ta sẽ có một lộ trình duy nhất cho SPA của chúng ta. Bạn cũng có thể thêm các đường dẫn khác tại đây trong trường hợp bạn muốn lập bản đồ / bia ​​ chẳng hạn như danh sách các loại bia, nhưng chúng tôi sẽ giữ cho nó đơn giản.

Ngoài ra, hãy lưu ý rằng chúng tôi đang nhập Home ở đây, chưa tồn tại. Vì vậy, hãy tạo nó thành Home.jsx trong thành phần thư mục. Sau đó, thêm mã sau vào nó:

import { Layout } from "antd";
import React from "react";
import Beers from "./Beers";
import Header from "./Header";

const { Content, Footer } = Layout;

export default () => (
  <Layout className="layout">
    <Header />
    <Content style={{ padding: "0 50px" }}>
      <div className="site-layout-content" style={{ margin: "100px auto" }}>
        <h1>Beer Catalog</h1>
        <Beers />
      </div>
    </Content>
    <Footer style={{ textAlign: "center" }}>Honeybadger ©2020.</Footer>
  </Layout>
);

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

Thành phần Home hoạt động giống như một trình hợp dịch; nó chứa tất cả các thành phần khác của ứng dụng, như Layout , Header , Content,Footer .

Điều quan trọng là phải phân biệt chính xác vị trí của từng mảnh này. Antd cung cấp một loạt các thành phần sẵn sàng, chẳng hạn như Bố cục, Nội dung và Chân trang, để tạo màn hình của bạn.

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

Thành phần Header

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

React on Rails:Xây dựng một ứng dụng đơn giản Các mục menu Antd.

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

import React from "react";
import { Layout, Menu } from "antd";

const { Header } = Layout;

export default () => (
  <Header>
    <div className="logo" />
    <Menu theme="dark" mode="horizontal" defaultSelectedKeys={["1"]}>
      <Menu.Item key="1">Home</Menu.Item>
      <Menu.Item key="2">Our Services</Menu.Item>
      <Menu.Item key="3">Contact</Menu.Item>
    </Menu>
  </Header>
);

Thành phần Antd Menu khá đơn giản để sử dụng nhưng bao gồm các tùy chọn tùy chỉnh có sẵn, cho phép chúng tôi tạo, ví dụ:ngăn kéo điều hướng, trình đơn thả xuống, nhóm và nhóm con.

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

Các menu của chúng tôi sẽ không điều hướng đến bất kỳ đâu; chúng sẽ chiếm màn hình chỉ để hoàn thành giao diện. Vì vậy, hãy chuyển sang Beers thành phần.

Thành phần Beers

Thành phần này tập trung vào danh sách các loại bia và các hành động có sẵn trong bảng, chẳng hạn như xóa, phân trang dữ liệu và tải lại bảng.

React on Rails:Xây dựng một ứng dụng đơn giản Trình bày trực quan các hành động và thành phần React.

Hãy nhìn vào hình ảnh trên. Chúng tôi đã chia nhỏ các thành phần và hành động xuống cấp thấp hơn, vì vậy bạn có thể hiểu rõ hơn những gì sẽ được thực hiện ở đây.

Trạng thái phản ứng

Các thành phần React được xây dựng với một state sự vật. Đối tượng này hoạt động như một cửa hàng được gắn trực tiếp vào thành phần đã cho. Mỗi thành phần có state riêng và mỗi khi bạn thay đổi trạng thái, thành phần sẽ được hiển thị lại.

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

state = {
  beers: [],
};

Danh sách các loại bia

Để cấp nguồn cho mảng này, chúng tôi sẽ cần truy xuất danh sách từ bộ điều khiển API mà chúng tôi đã tạo trước đó. Xem lại hàm sẽ truy xuất 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ì mục đích đơn giản, chúng tôi sẽ sử dụng API Tìm nạp có sẵn cho tất cả các trình duyệt hiện đại mỗi khi chúng tôi cần yêu cầu dữ liệu từ API.

Hàm trên thực hiện một vài bước để truy xuất mảng bia từ API:

  1. Đầu tiên, nó yêu cầu / index điểm cuối không đồng bộ và then kiểm tra xem trạng thái phản hồi có bằng OK không .
  2. Nếu đúng, chúng tôi trả về dữ liệu dưới dạng JSON; nếu không, hãy tạo ra một Error .
  3. Then , chúng tôi lặp lại mảng kết quả để tạo đối tượng bia của riêng chúng tôi và thêm vào beers của tiểu bang mảng.
  4. Nếu có bất kỳ sự cố nào xảy ra trong quá trình này, hãy catch khối sẽ ghi lại ngoại lệ và hiển thị nó dưới dạng một cảnh báo tin nhắn.

Đẹp, phải không? Đây là bước tương tự mà chúng tôi sẽ thực hiện cho tất cả các yêu cầu khác.

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

columns = [
  {
    title: "Brand",
    dataIndex: "brand",
    key: "brand",
  },
  ...{
    title: "",
    key: "action",
    render: (_text, record) => (
      <Popconfirm title="Are you sure to delete this beer?" onConfirm={() => this.deleteBeer(record.id)} okText="Yes" cancelText="No">
        <a href="#" type="danger">
          Delete{" "}
        </a>
      </Popconfirm>
    ),
  },
];

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

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

Đối với hầu hết các cột, cấu hình tương tự nhau, ngoại trừ cột hành động. Ở đó, chúng ta cần chỉ định liên kết hành động để kích hoạt khi người dùng muốn xóa một mục. Lưu ý rằng chúng tôi đang sử dụng thành phần Antd's Popconfirm.

Nó là một thành phần rất hay, tạo điều kiện thuận lợi cho công việc nhắc người dùng xác nhận một hành động trước khi nó xảy ra. Hình ảnh bên dưới minh họa nó trông như thế nào:

React on Rails:Xây dựng một ứng dụng đơn giản Nhắc hộp thoại xác nhận trước khi xóa.

Xóa Hành động

Để xóa một mục, chúng tôi sẽ cần thực hiện hai thao tác chính:lệnh xóa trên API và tải lại bảng.

Chức năng xóa tương tự như lần tìm nạp đầu tiên mà chúng tôi đã thực hiện:

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));
};

Nhìn thấy? điều mới duy nhất ở đây là phương thức HTTP App được truyền dưới dạng tham số thứ hai của fetch phương pháp. Ngoài ra, trong then , chúng tôi gọi là reloadBeers chức năng này sẽ tìm nạp lại tất cả các loại bia từ back-end một lần nữa.

Nội dung của hàm này khá nhiều như sau:

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

Chúng tôi đang đặt lại beers của tiểu bang mảng và gọi lại hàm tải.

Thành phần cuối cùng

Cuối cùng, chúng ta cần soạn thành phần bằng cách gọi các thẻ antd một cách rõ ràng. Hãy xem nó diễn ra như thế nào với mã thành phần 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) => (
        <Popconfirm title="Are you sure to delete this beer?" onConfirm={() => this.deleteBeer(record.id)} okText="Yes" cancelText="No">
          <a href="#" type="danger">
            Delete{" "}
          </a>
        </Popconfirm>
      ),
    },
  ];

  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 (
      <>
        <Table className="table-striped-rows" dataSource={this.state.beers} columns={this.columns} pagination={{ pageSize: 5 }} />

        <AddBeerModal reloadBeers={this.reloadBeers} />
      </>
    );
  }
}

export default Beers;

Bây giờ, bạn có thể nhìn thấy mọi thứ cùng nhau. Hàm kết xuất sẽ hiển thị hai thẻ mà chúng tôi đang nhập vào đó:Table của antd thành phần và AddBeerModal (biểu mẫu phương thức mà chúng tôi sẽ tạo trong vài phút nữa).

Thành phần bảng rất phong phú theo cách nó cho phép chúng tôi tự động phân trang thông qua các kết quả bằng cách đặt pagination sự vật. Thuộc tính duy nhất mà chúng tôi đang thêm ở đây là kích thước của mỗi trang (5 kết quả trên mỗi trang).

dataSource thuộc tính nhận danh sách các loại bia mà chúng tôi đã gắn kết từ back-end và các cột App thuộc tính nhận siêu dữ liệu mà chúng tôi đã tạo.

Thành phần AddBeerModal

Bên dưới bảng, bạn có thể thấy một nút để thêm các loại bia mới. Khi chúng tôi nhấp vào nút này, nó sẽ mở ra một phương thức có biểu mẫu để đăng ký các loại bia mới vào danh mục của chúng tôi, như bạn có thể thấy bên dưới:

React on Rails:Xây dựng một ứng dụng đơn giản Thêm các loại bia mới vào danh mục.

Đây là một cách tuyệt vời để khám phá cách antd cũng xử lý các biểu mẫu.

Trước tiên, hãy phân tích các hành động mà chúng ta sẽ thực hiện trong thành phần này. Lưu ý rằng bản thân thành phần được tạo ra từ hai thành phần:Nút và Phương thức.

Điều này có nghĩa là chúng tôi sẽ phải lập bản đồ các hoạt động liên quan đến cả hai:

  • showModalhandleCancel giải quyết việc mở và đóng của phương thức.
  • onFinish được kích hoạt khi chúng tôi gửi biểu mẫu.

Chúng sẽ chơi với trạng thái của thành phần, trạng thái này sẽ chỉ lưu trữ chuyển đổi phương thức (tức là nó có hiển thị hay không):

state = {
  visible: false,
};

Để hiển thị hoặc ẩn phương thức, chúng ta chỉ cần chuyển đổi Boolean này:

this.setState({
  visible: true,
});

Để gọi API của bia và đăng ký một loại bia mới, chúng tôi sẽ cần sử dụng lại API tìm nạp:

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 tiên chúng tôi gọi một yêu cầu trong đó chúng tôi gửi dữ liệu đến máy chủ. Trong trường hợp này, chúng tôi cũng sẽ cần nói rõ ràng với API loại thông tin nào đang hướng tới. Đó là lý do tại sao headers thuộc tính phải được thông báo.

Nếu mọi thứ suôn sẻ, chúng tôi chỉ cần đóng phương thức và tải lại danh sách của bảng.

Bây giờ, hãy cùng nhau xem mọi thứ, cùng với kết xuất thành phần:

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 (
      <>
        <Button type="primary" onClick={this.showModal}>
          Create New +
        </Button>

        <Modal title="Add New Beer ..." visible={this.state.visible} onCancel={this.handleCancel} footer={null}>
          <Form ref={this.formRef} layout="vertical" onFinish={this.onFinish}>
            <Form.Item name="brand" label="Brand" rules={[{ required: true, message: "Please input your beer brand!" }]}>
              <Input placeholder="Input your beer brand" />
            </Form.Item>

            <Form.Item name="style" label="Style" rules={[{ required: true, message: "Please input your beer style!" }]}>
              <Input placeholder="Input your beer style" />
            </Form.Item>

            <Form.Item
              name="country"
              label="Country"
              rules={[
                {
                  required: true,
                  message: "Please input the country of the beer!",
                },
              ]}
            >
              <Select showSearch placeholder="Select your beer country" optionFilterProp="children" style={{ width: "100%" }}>
                <Option value="Finland">Finland</Option>
                <Option value="Germany">Germany</Option>
                <Option value="Netherlands">Netherlands</Option>
                <Option value="UK">UK</Option>
                <Option value="USA">USA</Option>
                <Option value="Other">Other</Option>
              </Select>
            </Form.Item>

            <Form.Item name="quantity" label="Quantity" rules={[{ required: true, message: "Please input the quantity!" }]}>
              <Input type="number" placeholder="How many beers you desire?" />
            </Form.Item>

            <Form.Item>
              <Button type="primary" htmlType="submit">
                Submit
              </Button>
            </Form.Item>
          </Form>
        </Modal>
      </>
    );
  }
}

export default AddBeerModal;

Antd allows us to specify each form’s item rules individually. If a field is required, just say so by providing a rules attribute. You can customize the message it’ll display in case the user submits the form without filling it properly:

React on Rails:Xây dựng một ứng dụng đơn giản Validating form inputs.

Take a look at the Select component, which translates a combo box. See how easy it is to create complex components by just providing the right attributes. For example, if you want to make your select searchable, just put the showSearch property, there and it’s done:

React on Rails:Xây dựng một ứng dụng đơn giản Filtering results within a Select.

Antd will automatically filter the select options based on your input.

Styling

Sometimes, you’ll need to provide some CSS styling to components that do not provide a default (like antd’s table) or customize the ones that come built-in.

To do this, you can create as many CSS files as you want and organize them in a structure that pleases you. Rails already create an application.css file, under the app/assets/stylesheets folder. Open it and the following content:

.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;
}

Those are the CSS rules to make our table stripped, for example. Feel free to add as many extra styles here as you want.

Kiểm tra

Before heading to the tests, we need to disable the CSRF token checking that Rails automatically configures for our apps. To do so, go to the app/controllers/application_controller.rb file and change it to the following:

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

This way, we avoid having to validate the tokens each time we perform a request.

Tuyệt quá! Now, start your server via rails s command, access the https://localhost:3000/ address, and play around with the CRUD.

Kết luận

As a homework task, I’d recommend that you try implementing the update functionality of the CRUD. You can adapt the edit method at the API controller to receive the updated beer info and perform the update to the database. For the view, another modal would suit very well to accommodate the edit’s form.

You can also find the source code for this tutorial here. Good studies!