Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

React on Rails:สร้างแอพอย่างง่าย

บริษัทที่สร้างแอปพลิเคชันส่วนหน้าทั้งหมดมักเลือกกรอบงานเดียวกัน เช่น Rails เพื่อสร้างส่วนหลัง นี่เป็นตัวเลือกที่ดีที่สุดและน่าเชื่อถือที่สุดมาหลายปีแล้ว

ทุกวันนี้ ไลบรารีและเฟรมเวิร์กจำนวนมากในจักรวาลส่วนหน้าที่มีการพัฒนาอย่างต่อเนื่อง ช่วยให้นักพัฒนาสามารถเลือกแพลตฟอร์มที่แตกต่างกันสำหรับทั้งแบ็คเอนด์และฟรอนต์เอนด์และรวมเข้าด้วยกันได้อย่างง่ายดาย

React ได้กลายเป็นไททันของ Pangea ส่วนหน้า หากคุณทำงานกับ Ruby on Rails คุณอาจจำเป็นต้องเปลี่ยนหน้า Rails เริ่มต้นเป็นโค้ด React (หรือเฟรมเวิร์กส่วนหน้าอื่นๆ) บางทีคุณอาจแค่ชอบฟีเจอร์ Rails + React และชอบที่จะรวมพลังของเทคโนโลยีทั้งสองไว้ในแอปเดียว

และก็ไม่เป็นไร! บทความนี้มีจุดมุ่งหมายเพื่อสำรวจตัวอย่างในโลกแห่งความเป็นจริง:แอปพลิเคชัน CRUD ที่จัดการสินค้าคงคลังของผลิตภัณฑ์เบียร์ มันจะถูกสร้างขึ้นในขั้นต้นด้วย Rails จากนั้นส่วนหน้าใหม่ที่สร้างด้วย React จะถูกรวมเข้าด้วยกันเพิ่มเติม

React on Rails:สร้างแอพอย่างง่าย แอปพลิเคชัน Beer CRUD

ในไม่กี่ขั้นตอน คุณจะเข้าใจส่วนสำคัญของ React วิธีที่ Rails นำมาใช้ และวิธีที่คุณสามารถเริ่มต้นการรวม Rails และ React เข้ากับโครงการในอนาคตของคุณ

ตั้งค่า

คุณต้องมีสภาพแวดล้อมที่ตั้งค่าด้วย Ruby (และ Rails), Node.js และ Yarn ตามข้อกำหนดเบื้องต้น

คุณสามารถเลือก npm เพื่อจัดการแพ็คเกจด้านหน้าได้ แต่เราจะยึดตาม Yarn เพื่อความเรียบง่าย

ในโฟลเดอร์ที่คุณต้องการ ให้รันคำสั่งต่อไปนี้:

rails new crud-rails-react

นี่จะเป็นการเริ่มต้นโครงการ Rails ของเรา จากนั้น เปิดโครงการที่สร้างขึ้นใน IDE ของคุณและไปที่ Gemfile . โดยตรง .

เราจำเป็นต้องแก้ไขข้อผิดพลาดที่คำสั่งนี้สร้างขึ้นสำหรับ SQLite gem ดังนั้น อย่าลืมค้นหา sqlite3 อัญมณีและเปลี่ยนเป็นดังต่อไปนี้:

gem 'sqlite3', '~> 1.3.10'

ซึ่งจะช่วยป้องกันข้อผิดพลาดที่ทราบเกี่ยวกับการกำหนดเวอร์ชันของฐานข้อมูล เนื่องจากเราจะใช้ SQLite เป็นฐานข้อมูลเริ่มต้นสำหรับตัวอย่าง CRUD

ไม่ต้องกังวล ในฐานข้อมูลอื่น ปัญหานี้จะไม่เกิดขึ้น

การกำหนดค่าฐานข้อมูล

ฉันมักจะชอบสร้างสิ่งต่าง ๆ จากฐานถึงชั้นบนสุด เรามาเริ่มสร้างโมเดลฐานข้อมูลกันเถอะ

เราต้องการเพียงอันเดียว ดังนั้นจึงไม่มีฟีเจอร์คำสั่งสำหรับงานที่ดีไปกว่าการนั่งร้าน Rails:

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

โมเดลนี้ค่อนข้างเรียบง่าย ดังนั้นคุณสามารถเพิ่มแอตทริบิวต์และประเภทอื่นๆ ได้ตามต้องการ

ภายใน db/migrate โฟลเดอร์ มีไฟล์ใหม่ที่ลงท้ายด้วย “_create_beers.rb ” นี่คือบันทึกที่ Rails สร้างขึ้นเพื่อเป็นตัวแทนของเบียร์

ในทางกลับกัน โมเดลจะถูกสร้างขึ้นภายใต้ app/models โฟลเดอร์ ปล่อยให้มันเป็นอย่างนั้นและเพิ่มบรรทัดของรหัสต่อไปนี้ใน 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)

ไฟล์นี้จะจัดเก็บโหลดข้อมูลเริ่มต้นสำหรับฐานข้อมูลเมื่อแอปเริ่มทำงาน หากต้องการเผยแพร่ลงในฐานข้อมูล ให้รันคำสั่งต่อไปนี้:

rake db:seed

แค่นั้นแหละ! ตอนนี้คุณมีเบียร์อยู่ที่โต๊ะแล้ว .

การตั้งค่าเว็บแพ็คเกอร์

Webpacker เป็นหนึ่งในกลุ่มโมดูลสแตติกที่ใช้กันอย่างแพร่หลายมากที่สุดสำหรับแอปพลิเคชัน JavaScript ด้วยเหตุนี้ จึงเหมาะที่จะรวมความสามารถของ React เข้ากับแอปพลิเคชันที่มีอยู่

Rails ยังมอบ Webpacker Bundler ที่ปรับให้เข้ากับการจัดการแอปพลิเคชันที่เหมือน JavaScript ภายใน Rails ได้อย่างเต็มที่

หากต้องการติดตั้ง ให้เพิ่มบรรทัดที่สองใน Gemfile . ของคุณ ดังต่อไปนี้:

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

ยอดเยี่ยม! นี่เป็นอัญมณีเดียวที่เราจะต้องเพิ่มสำหรับการพัฒนาแอปทั้งหมด เป็นไปได้เพียงเพราะเรากำลังมอบหมายหน้าที่รับผิดชอบให้กับ Yarn ซึ่งจะระบุไว้ในบทความนี้ในภายหลัง

ตอนนี้ ได้เวลาติดตั้งการอัปเดตโดยออกคำสั่งต่อไปนี้:

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

อันแรกเป็นที่รู้จักกันดีในหมู่นักพัฒนา Rails ส่วนใหญ่ เราเพียงแค่ติดตั้งการพึ่งพาทั้งหมด รวมถึง Webpacker ด้วย

เมื่อติดตั้ง Webpacker แล้ว เราสามารถจำลองคำสั่งผ่าน Rake เพื่อติดตั้งการพึ่งพา Webpacker รวมถึงคำสั่ง React

นี่เป็นขั้นตอนที่สำคัญมากเพราะนี่คือที่ที่ Webpacker จะทำให้แน่ใจว่าการพึ่งพา JavaScript ทั้งหมดของคุณได้รับการตั้งค่าอย่างเหมาะสมกับสภาพแวดล้อม Rails ดังนั้นอย่าข้ามหรือลองเรียกใช้โดยตรงเหมือนที่คุณมักจะทำกับ npm หรือ Yarn ใช่ไหม

เมื่อคำสั่งเสร็จสิ้น สองโฟลเดอร์และไฟล์ (เช่น node_modules และ package.json ) ก็จะถูกสร้างขึ้นด้วย

การตั้งค่าส่วนหน้า

การตั้งค่าทั้งหมดที่เราได้ทำขึ้นนั้นเพียงพอที่จะให้แอปพลิเคชัน CRUD ใช้งานได้กับ Rails เท่านั้น หากคุณเริ่มเซิร์ฟเวอร์ Rails ผ่าน rails s คำสั่งนี้จะเป็นผลลัพธ์:

React on Rails:สร้างแอพอย่างง่าย ราง CRUD เบียร์ที่สร้างโดยอัตโนมัติ

อย่างไรก็ตาม เราต้องการ CRUD ของเราเองด้วย React

ขั้นแรก คุณต้องตรวจสอบให้แน่ใจว่าการพึ่งพาส่วนหน้าที่จำเป็นทั้งหมดได้รับการกำหนดค่าผ่าน Yarn:

  • ตอบสนอง
  • React Router เพื่อจัดการกับการนำทางในแอป React
  • การออกแบบมดสำหรับส่วนประกอบ React ที่พร้อมใช้งาน

Ant Design (รู้จักกันในชื่อ antd) เป็นไลบรารีโอเพนซอร์ซที่หลากหลายสำหรับแอปพลิเคชันระดับองค์กร มันมีส่วนประกอบ React ที่ปรับแต่งได้สูงมากมาย ซึ่งทำให้การพัฒนาเว็บแอปง่ายขึ้นมาก

หากต้องการติดตั้งทุกอย่าง ให้เรียกใช้คำสั่งต่อไปนี้:

yarn add antd react-router-dom

เราไม่จำเป็นต้องเพิ่ม react . อย่างชัดเจน ไลบรารี่ตั้งแต่ react-router-dom จะทำ

ณ จุดนี้ เมื่อคุณเปิด package.json ไฟล์นี้จะเป็นเนื้อหาที่สร้างขึ้นโดยอัตโนมัติ:

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

นั่นคือการตั้งค่าพื้นฐานที่สุดสำหรับคอมโบ Rails-React ดังนั้น เรามาเริ่มใช้โค้ดกันเลยดีกว่า

การปรับรางด้านข้าง

การดำเนินการที่สำคัญบางอย่างต้องได้รับการแก้ไขที่ฝั่ง Rails ก่อนดำเนินการต่อไป

อันดับแรก เราต้องกำหนดตัวควบคุมที่จะรวมศูนย์การเปลี่ยนเส้นทางของหน้า เนื่องจากเรากำลังสร้างแอปพลิเคชันหน้าเดียว (SPA) จึงจำเป็นต้องมีตัวควบคุมเพียงตัวเดียว ซึ่งสร้างไว้แล้ว:BeersController .

เปิดภายใต้ app/controllers โฟลเดอร์และเปลี่ยนเนื้อหาดังนี้:

class BeersController < ApplicationController
  def index
  end
end

ไม่ต้องกังวล; โค้ดทั้งหมดที่เราลบไปจะถูกวางไว้ในตัวควบคุมถัดไปที่จะสร้าง

ฟังก์ชันเดียวของคอนโทรลเลอร์นี้คือการจัดหาเส้นทางตรงจาก Rails ไปยัง React ซึ่งเป็นสาเหตุที่เราตั้งค่าเฉพาะ index วิธีการ

หากต้องการเชื่อมต่อโดยตรง ให้เปิด routes.rb ไฟล์ภายใต้ config โฟลเดอร์และเปลี่ยนเนื้อหาดังต่อไปนี้:

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

สังเกต root . ใหม่ การกำหนดค่า ใช่ เรากำลังจับคู่ปลายทางของรูทกับ beers วิธีดัชนี

คุณจะต้องล้าง index.html.erb . ด้วย ภายใน app/views/beers โฟลเดอร์ เนื่องจากเราไม่ต้องการให้แสดงเนื้อหาเว็บ Rails ใดๆ เป็นเคล็ดลับที่เราใช้บังคับ Rails ให้แสดงโค้ด React เท่านั้น

เบียร์ API

ตอนนี้ ไปที่การสร้าง Beer API โครงสร้าง API จะเหมือนกับ BeersController แต่มีการเปลี่ยนแปลงเล็กน้อย หากต้องการสร้าง ให้รันคำสั่งต่อไปนี้:

rails generate controller api/v1/Beers

อย่าลืมสร้างโดยใช้ระบบการกำหนดเวอร์ชัน สิ่งนี้จะช่วยให้คุณพัฒนา API ของคุณในอนาคตและแยกความแตกต่างจากตัวควบคุมทั่วไป

ตอนนี้ เปิด app/controllers/api/v1/beers_controller.rb ไฟล์และแทนที่โค้ดด้วยสิ่งต่อไปนี้:

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

การดำเนินการส่วนใหญ่รีไซเคิลจากตัวควบคุมก่อนหน้า

before_action ข้อมูลโค้ดจะดูแลการกู้คืนวัตถุเบียร์ที่เหมาะสมตาม id พารามิเตอร์ภายในคำขอ เฉพาะการดำเนินการที่วางลงในอาร์เรย์หลัง :only ประโยคจะต้องใช้คุณลักษณะการกู้คืนอัตโนมัตินี้

วิธีที่เหลือจะเทียบเท่ากับการดำเนินการของ CRUD แต่ละรายการ อย่าลืมส่งคืน JSON เพื่อตอบสนองต่อคำขอของคุณเสมอ เพราะนั่นคือรูปแบบที่เราจะใช้ในส่วนประกอบ React ของเรา

สุดท้าย คุณจะต้องปรับ config/routes.rb อีกครั้งเพื่อรวมเส้นทางที่สร้างขึ้นใหม่ ดังนั้น อย่าลืมเปลี่ยนเนื้อหาไฟล์ดังต่อไปนี้:

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

เส้นทางการทำลายแสดงให้เห็นวิธีการแมปไปยัง destroy เมธอดหากไม่ได้กำหนดไว้อย่างชัดเจนในเส้นทาง

ส่วนประกอบที่ทำปฏิกิริยา

React ทำงานผ่านส่วนประกอบต่างๆ ซึ่งทำหน้าที่เหมือนส่วนประกอบสำคัญของเว็บแอปพลิเคชัน แต่ละคนทำงานอย่างน้อยหนึ่งอย่างตามบริบท

กล่าวโดยย่อ ส่วนประกอบถูกสร้างขึ้นจากคลาส JavaScript หรือฟังก์ชันที่สามารถรับคุณสมบัติเป็นพารามิเตอร์ ประมวลผลตรรกะทางธุรกิจภายใน และในท้ายที่สุด ส่งคืนคอมโพเนนต์ UI ที่ใช้งานได้ซึ่งแสดงถึงส่วนหนึ่งของหน้าจอ

ใช้ข้อมูลโค้ดต่อไปนี้ที่ดึงมาจากตัวอย่างต่อไปที่เราจะสร้าง:

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

ใช่ ส่วนประกอบ React ใช้ภาษามาร์กอัปแบบกำหนดเองที่เรียกว่า JSX (JavaScript XML) ที่มีลักษณะคล้ายกับ HTML แท็ก HTML ทั้งหมดมีอยู่ในไฟล์ JSX ด้วย คุณสามารถอ่านเพิ่มเติมเกี่ยวกับ JSX ได้ที่นี่

ตัวอย่างข้างต้นแสดงให้เห็นว่า antd จัดการกับองค์ประกอบโครงร่างโครงสร้างอย่างไร ส่วนประกอบต่างๆ ประกอบขึ้นจากส่วนประกอบอื่นๆ และซ้อนกันเป็นองค์ประกอบทั้งหมด บางคนได้รับคุณสมบัติ (ไม่บังคับหรือไม่) และบางส่วนสามารถมีเนื้อหาภายในได้

คอมโพเนนต์สามารถเป็นแบบคลาสหรือแบบอิงตามฟังก์ชันได้

ส่วนประกอบของคลาส

คอมโพเนนต์ตามคลาสจะถูกสร้างขึ้นตามคลาส JavaScript ปกติ ดังที่แสดงด้านล่าง:

class Beers extends React.Component {}

พวกมันสืบทอดมาจาก React.Component มีวงจรชีวิต และจัดเตรียมวิธีการใช้งานเพื่อรันโค้ดก่อนการเริ่มต้น แสดงผล และทำลายเฟส

อย่างไรก็ตาม วิธีที่สำคัญที่สุด (และจำเป็น) คือ render() ซึ่งเรียกทุกครั้งที่มีการอัปเดตส่วนประกอบ

ส่วนประกอบการทำงาน

ส่วนประกอบที่ใช้งานได้ใช้ประโยชน์จากฟังก์ชันลูกศรของ ES6 และทำให้ส่วนประกอบ React ง่ายขึ้นในแง่ของรูปแบบและความซับซ้อน

beersเหมือนกัน องค์ประกอบด้านบนจะแสดงเป็นฟังก์ชันดังนี้:

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

มันง่ายกว่ามากใช่ไหม

การตั้งค่าปฏิกิริยา

เราได้ล้างหน้าดัชนี Rails แล้ว ตอนนี้ เราจะบอกให้ Rails รู้ว่าจำเป็นต้องทำให้ React เป็น front-end เริ่มต้น

เพื่อให้บรรลุสิ่งนี้ คุณต้องเพิ่มโค้ดบรรทัดต่อไปนี้ใน &lt;head> แท็กของ app/views/layouts/application.html.erb . ของคุณ ไฟล์:

<%= javascript_pack_tag 'index' %>

การดำเนินการนี้จะเพิ่มชุด JavaScript ลงในส่วนหัวของแอปพลิเคชันของเรา ซึ่งจะทำให้ไฟล์ JavaScript ทั้งหมด รวมทั้งไฟล์ React ทำงานภายใน ดัชนี หน้า.

เราจำเป็นต้องตรวจสอบให้แน่ใจว่า index.jsx ไฟล์มีชื่อเดียวกันเนื่องจากชี้ไปที่แพ็กนำเข้า

เพื่อจุดประสงค์นี้ เรามาเปลี่ยนชื่อ app/javascript/packs/hello_react.jsx ที่สร้างอัตโนมัติ ไฟล์ไปที่ index.jsx .

จากนั้นให้แทนที่โค้ดด้วยสิ่งต่อไปนี้:

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

อย่าเข้าใจผิดว่าไฟล์นี้เป็นไฟล์แอปพลิเคชัน React เนื่องจากเป็นเพียงไฟล์ที่จะโหลดลำดับชั้นของแอป React ทั้งหมดลงใน DOM ผ่าน render ของ ReactDOM ฟังก์ชัน

โดยปกติ ทุกแอปพลิเคชัน React จะเริ่มต้นจาก index.js ไฟล์ที่โหลดทุกอย่างที่จำเป็น รวมทั้ง React เองด้วย

App แท็กจะจับคู่องค์ประกอบด้านบนของลำดับชั้นของเรา มาสร้างเป็น index.jsx ภายใต้ javascript/ส่วนประกอบ โฟลเดอร์ (สร้างโฟลเดอร์ด้วยตนเองในกรณีที่ยังไม่มี) และใส่รหัสต่อไปนี้ลงไป:

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

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

หรือคุณสามารถนำเข้าไฟล์ antd CSS ภายใน index.jsx . ทั้งสองวิธีก็ใช้ได้

รายการเส้นทางอยู่ภายใต้ เส้นทาง โฟลเดอร์ พวกมันดึงมาจากไลบรารี React Router ซึ่งทำงานหนักส่วนใหญ่สำหรับเรา นี่คือเนื้อหา:

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

แต่ละเส้นทางของคุณต้องถูกแมปภายใน Route . ที่แตกต่างกัน แท็ก Route พารามิเตอร์ต้องตรงกับ URI ของแต่ละปลายทางของเส้นทาง ขณะที่ component param ระบุส่วนประกอบที่ React Router ควรเปลี่ยนเส้นทางคำขอ

จำไว้ว่าเราจะมีเส้นทางเดียวสำหรับสปาของเรา คุณยังสามารถเพิ่มเส้นทางอื่นๆ ที่นี่ ในกรณีที่คุณต้องการแมป /beers สำหรับรายการเบียร์ เป็นต้น แต่เราจะทำให้มันเรียบง่าย

นอกจากนี้ โปรดทราบว่าเรากำลังนำเข้า Home องค์ประกอบที่นี่ซึ่งยังไม่มีอยู่ มาสร้างเป็น Home.jsx ภายใต้ ส่วนประกอบ โฟลเดอร์ จากนั้นเพิ่มรหัสต่อไปนี้ลงไป:

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

เมื่อพูดถึงส่วนประกอบ React ฉันชอบที่จะสร้างจากบนลงล่าง ด้วยวิธีนี้ คุณสามารถดูส่วนประกอบที่จำเป็นทั้งหมดเพื่อทำให้แอปทำงานได้โดยรวม

คอมโพเนนต์ Home ทำงานเหมือนแอสเซมเบลอร์ รองรับส่วนประกอบอื่นๆ ทั้งหมดของแอป เช่น Layout , Header , Content, และ Footer .

สิ่งสำคัญคือต้องแยกความแตกต่างอย่างเหมาะสมว่าชิ้นส่วนเหล่านี้มาจากไหน Antd มีองค์ประกอบที่พร้อมใช้งานมากมาย เช่น เค้าโครง เนื้อหา และส่วนท้าย เพื่อเขียนหน้าจอของคุณ

โดยเน้นที่โครงสร้างของส่วนของหน้าเป็นหลัก แต่บางส่วนยังมีรูปแบบ CSS ในตัว ซึ่งจะเป็นประโยชน์ต่อเราด้วยรูปลักษณ์ที่ดีขึ้น

ส่วนประกอบส่วนหัว

ไฟล์คอมโพเนนต์ Header.jsx ซึ่งควรสร้างขึ้นภายใน javascript/components โฟลเดอร์จะเก็บเนื้อหาของส่วนหัวไว้ ประกอบด้วยเมนู antd ที่เรียบง่ายและ div ที่มีโลโก้ Honeybadger ดังที่แสดงด้านล่าง:

React on Rails:สร้างแอพอย่างง่าย รายการเมนู Antd

ด้านล่างนี้ คุณสามารถค้นหาโค้ดที่จะวางลงใน 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>
);

คอมโพเนนต์ Antd Menu ค่อนข้างใช้งานง่าย แต่มีตัวเลือกการปรับแต่งที่กว้างขวาง ทำให้เราสามารถสร้างลิ้นชักการนำทาง รายการแบบเลื่อนลง กลุ่ม และกลุ่มย่อยได้

โปรดทราบว่าเรากำลังให้บริการ defaultSelectedKeys , อาร์เรย์ที่บอกเมนูว่ารายการใดกำลังทำงานอยู่

เมนูของเราจะไม่นำทางไปที่ใด พวกเขาจะครอบครองหน้าจอเพื่อเติมเต็มรูปลักษณ์เท่านั้น งั้นไปที่ Beers . กัน ส่วนประกอบ

ส่วนประกอบของเบียร์

องค์ประกอบนี้เน้นที่รายการเบียร์และการดำเนินการที่มีอยู่ในตาราง เช่น การลบ การแบ่งหน้าข้อมูล และการโหลดตารางใหม่

React on Rails:สร้างแอพอย่างง่าย การแสดงการกระทำและส่วนประกอบต่างๆ ของปฏิกิริยา

ลองดูที่ภาพด้านบน เราได้แบ่งองค์ประกอบและการดำเนินการต่างๆ ออกเป็นระดับที่ต่ำกว่า เพื่อให้คุณเข้าใจสิ่งที่จะดำเนินการที่นี่ได้ดีขึ้น

สถานะปฏิกิริยา

ส่วนประกอบ React ถูกสร้างขึ้นด้วย state วัตถุ. ออบเจ็กต์นี้ทำหน้าที่เป็นร้านค้าที่เชื่อมต่อโดยตรงกับส่วนประกอบที่กำหนด แต่ละองค์ประกอบมี state . ของตัวเอง ออบเจ็กต์ และทุกครั้งที่คุณเปลี่ยนสถานะ คอมโพเนนต์จะได้รับการแสดงผลใหม่

การกระทำแรกของ beers . ของเรา องค์ประกอบคือการแสดงรายการบนโต๊ะ เพื่อจุดประสงค์นี้ เราจะต้องเก็บรายการนี้ไว้ในอาร์เรย์:

state = {
  beers: [],
};

รายการเบียร์

ในการป้อนอาร์เรย์นี้ เราจะต้องดึงรายการจากตัวควบคุม API ที่เราสร้างไว้ก่อนหน้านี้ ตรวจสอบฟังก์ชันที่จะดึงข้อมูล:

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

เพื่อความง่าย เราจะใช้ Fetch API ที่ใช้ได้กับเบราว์เซอร์สมัยใหม่ทุกเครื่องทุกครั้งที่ต้องการขอข้อมูลจาก API

ฟังก์ชันด้านบนใช้ไม่กี่ขั้นตอนในการดึงอาร์เรย์ของเบียร์จาก API:

  1. มันขอ /index . ก่อน จุดปลายแบบอะซิงโครนัสและ then ตรวจสอบว่าสถานะการตอบสนองเท่ากับ ตกลง .
  2. ถ้าเป็นเช่นนั้น เราจะส่งคืนข้อมูลเป็น JSON; มิฉะนั้น เราจะโยน Error .
  3. then เราวนซ้ำอาร์เรย์ของผลลัพธ์เพื่อเขียนวัตถุเบียร์ของเราเองและเพิ่มไปยัง beers ของรัฐ อาร์เรย์
  4. หากมีสิ่งใดผิดพลาดระหว่างกระบวนการ catch block จะจับข้อยกเว้นและแสดงเป็นข้อความแจ้งเตือน

ดีใช่มั้ย? นี่เป็นขั้นตอนเดียวกับที่เราจะดำเนินการกับคำขออื่นๆ ทั้งหมด

แต่ antd แสดงข้อมูลในตารางอย่างไร คำถามที่ดี! มาดูโค้ดต่อไปนี้กัน:

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

ฉันได้ลดความซับซ้อนลงเล็กน้อยเพื่อความเข้าใจที่ดีขึ้น นี่คืออาร์เรย์ที่แสดงถึงโครงกระดูกของตารางของเรา นี่คือวิธีการทำงานของตาราง antd พวกเขาจำเป็นต้องได้รับข้อมูลเมตาดาต้าเกี่ยวกับโครงสร้างตารางของคุณ (แถวและคอลัมน์) เป็นอาร์เรย์

แต่ละคอลัมน์เป็นวัตถุภายในอาร์เรย์ และลำดับมีความสำคัญที่นี่ title แอตทริบิวต์ได้รับชื่อของคอลัมน์ในขณะที่ dataIndex ชื่อจะเป็นที่รู้จักภายในส่วนประกอบ React และ key เป็นตัวระบุเฉพาะ

สำหรับคอลัมน์ส่วนใหญ่ การกำหนดค่าจะคล้ายกัน ยกเว้นคอลัมน์การดำเนินการ ที่นั่น เราจำเป็นต้องระบุลิงก์ของการดำเนินการที่จะทริกเกอร์เมื่อผู้ใช้ต้องการลบรายการ โปรดทราบว่าเรากำลังใช้ประโยชน์จากองค์ประกอบ Popconfirm ของ antd

เป็นองค์ประกอบที่ดีมากที่ช่วยอำนวยความสะดวกในการแจ้งให้ผู้ใช้ยืนยันการดำเนินการก่อนที่จะเกิดขึ้น ภาพด้านล่างแสดงให้เห็นว่ามีลักษณะอย่างไร:

React on Rails:สร้างแอพอย่างง่าย แสดงข้อความยืนยันก่อนที่จะลบ

ลบการกระทำ

ในการลบรายการ เราจะต้องดำเนินการหลักสองอย่าง:การเรียกการลบบน API และการโหลดตารางใหม่

ฟังก์ชันการลบคล้ายกับการดึงข้อมูลครั้งแรกของเรา:

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

ดู? สิ่งเดียวที่ใหม่ที่นี่คือ HTTP method ส่งผ่านเป็นพารามิเตอร์ที่สองของ fetch กระบวนการ. นอกจากนี้ ภายใน then ข้อ เราเรียก reloadBeers ฟังก์ชั่นซึ่งจะดึงเบียร์ทั้งหมดจากแบ็กเอนด์อีกครั้ง

เนื้อหาของฟังก์ชันนี้มีดังต่อไปนี้:

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

เรากำลังรีเซ็ต beers . ของรัฐ อาร์เรย์และเรียกใช้ฟังก์ชันโหลดอีกครั้ง

องค์ประกอบสุดท้าย

สุดท้าย เราต้องสร้างส่วนประกอบโดยการเรียกแท็ก antd อย่างชัดเจน มาดูกันว่ามันไปพร้อมกับโค้ดส่วนประกอบสุดท้ายอย่างไร:

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;

ตอนนี้คุณสามารถดูทุกอย่างร่วมกัน ฟังก์ชันการแสดงผลจะแสดงแท็กสองแท็กที่เรากำลังนำเข้า:Table ของ antd ส่วนประกอบและ AddBeerModal (รูปแบบโมดอลที่เราจะสร้างขึ้นในอีกไม่กี่นาที)

องค์ประกอบของตารางนั้นสมบูรณ์มากในลักษณะที่ช่วยให้เราสามารถแบ่งหน้าผลลัพธ์โดยอัตโนมัติด้วยการตั้งค่า pagination วัตถุ. พร็อพเพอร์ตี้เดียวที่เรากำลังเพิ่มที่นี่คือขนาดของแต่ละหน้า (5 ผลลัพธ์ต่อหน้า)

dataSource แอตทริบิวต์รับรายการเบียร์ที่เราได้ติดตั้งจากส่วนหลังและ columns แอตทริบิวต์ได้รับข้อมูลเมตาที่เราได้สร้างไว้แล้ว

ส่วนประกอบ AddBeerModal

ด้านล่างโต๊ะ คุณจะเห็นปุ่มสำหรับเพิ่มเบียร์ใหม่ เมื่อเราคลิกที่ปุ่มนี้ จะเป็นการเปิด modal พร้อมแบบฟอร์มการลงทะเบียนเบียร์ใหม่ลงในแค็ตตาล็อกของเรา ดังที่คุณเห็นด้านล่าง:

React on Rails:สร้างแอพอย่างง่าย การเพิ่มเบียร์ใหม่ลงในแคตตาล็อก

นี่เป็นวิธีที่ยอดเยี่ยมในการสำรวจว่า antd จัดการกับรูปแบบอย่างไร

ขั้นแรก มาแยกย่อยการดำเนินการที่เราจะมีในองค์ประกอบนี้ โปรดทราบว่าส่วนประกอบนั้นสร้างขึ้นจากสองส่วน:ปุ่มและโมดอล

ซึ่งหมายความว่าเราจะต้องแมปการดำเนินการที่เกี่ยวข้องกับทั้งสองสิ่งนี้:

  • showModal และ handleCancel จัดการกับการเปิดและปิดของกิริยาช่วย
  • onFinish ถูกเรียกเมื่อเราส่งแบบฟอร์ม

พวกเขาจะเล่นกับสถานะของส่วนประกอบ ซึ่งจะเก็บเฉพาะการสลับโมดอล (เช่น ไม่ว่าจะมองเห็นหรือไม่):

state = {
  visible: false,
};

หากต้องการแสดงหรือซ่อนโมดอล เราเพียงแค่สลับบูลีนนี้:

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

หากต้องการเรียก Beer's API และลงทะเบียนเบียร์ใหม่ เราจำเป็นต้องใช้ Fetch API อีกครั้ง:

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

นี่เป็นครั้งแรกที่เราเรียกคำขอที่เราส่งข้อมูลไปยังเซิร์ฟเวอร์ ในกรณีนี้ เราจะต้องบอก API ให้ชัดเจนด้วยว่าข้อมูลประเภทใดกำลังมุ่งหน้าไป นั่นเป็นเหตุผลที่ Header ต้องแจ้งแอตทริบิวต์

หากทุกอย่างเป็นไปด้วยดี เราก็เพียงแค่ปิดโมดอลและโหลดรายการของตารางใหม่

ทีนี้มาดูทุกอย่างพร้อม ๆ กันพร้อมกับการเรนเดอร์องค์ประกอบ:

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:สร้างแอพอย่างง่าย 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:สร้างแอพอย่างง่าย 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 โฟลเดอร์ 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.

กำลังทดสอบ

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.

ยอดเยี่ยม! Now, start your server via rails s command, access the https://localhost:3000/ address, and play around with the CRUD.

บทสรุป

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!