Cách thiết lập dự án Ruby on Rails với React Frontend
Ruby on Rails là một khung ứng dụng web phía server phổ biến, với hơn 42.000 sao trên GitHub tại thời điểm viết hướng dẫn này. Nó hỗ trợ rất nhiều ứng dụng phổ biến tồn tại trên web ngày nay, như GitHub , Basecamp , SoundCloud , Airbnb và Twitch . Với sự nhấn mạnh vào kinh nghiệm của lập trình viên và cộng đồng đam mê đã xây dựng xung quanh nó, Ruby on Rails sẽ cung cấp cho bạn các công cụ bạn cần để xây dựng và duy trì ứng dụng web hiện đại của bạn .React là một thư viện JavaScript được sử dụng để tạo giao diện user front-end. Được hỗ trợ bởi Facebook, nó là một trong những thư viện front-end phổ biến nhất được sử dụng trên web hiện nay. React cung cấp các tính năng như Mô hình đối tượng tài liệu ảo (DOM) , kiến trúc thành phần và quản lý trạng thái, giúp quá trình phát triển front-end có tổ chức và hiệu quả hơn.
Với giao diện user của web hướng tới các khung công tác tách biệt với mã phía server , việc kết hợp sự sang trọng của Rails với hiệu quả của React sẽ cho phép bạn xây dựng các ứng dụng mạnh mẽ và hiện đại theo xu hướng hiện tại. Bằng cách sử dụng React để hiển thị các thành phần từ bên trong chế độ xem Rails thay vì công cụ mẫu Rails, ứng dụng của bạn sẽ được hưởng lợi từ những tiến bộ mới nhất trong phát triển JavaScript và front-end trong khi vẫn tận dụng tính biểu cảm của Ruby on Rails.
Trong hướng dẫn này, bạn sẽ tạo một ứng dụng Ruby on Rails lưu trữ các công thức nấu ăn yêu thích của bạn, sau đó hiển thị chúng bằng giao diện user React. Khi hoàn tất, bạn có thể tạo, xem và xóa các công thức nấu ăn bằng giao diện React theo kiểu Bootstrap :
Nếu bạn muốn xem mã cho ứng dụng này, hãy xem kho lưu trữ đồng hành cho hướng dẫn này trên GitHub Cộng đồng DigitalOcean .
Yêu cầu
Để làm theo hướng dẫn này, bạn cần có những điều sau:
Node.js và npm được cài đặt trên máy phát triển của bạn. Hướng dẫn này sử dụng Node.js version 10.16.0 và npm version 6.9.0. Node.js là một môi trường thời gian chạy JavaScript cho phép bạn chạy mã của bạn bên ngoài trình duyệt. Nó đi kèm với Trình quản lý gói được cài đặt sẵn có tên là npm , cho phép bạn cài đặt và cập nhật các gói. Để cài đặt chúng trên macOS hoặc Ubuntu 18.04, hãy làm theo các bước trong Cách cài đặt Node.js và Tạo môi trường phát triển local trên macOS hoặc phần “Cài đặt bằng PPA” của Cách cài đặt Node.js trên Ubuntu 18.04 .
Trình quản lý gói Yarn được cài đặt trên máy phát triển của bạn, sẽ cho phép bạn download khung React. Hướng dẫn này đã được thử nghiệm trên version 1.16.0; để cài đặt phần phụ thuộc này, hãy làm theo hướng dẫn cài đặt Yarn chính thức .
Cài đặt khuôn khổ Ruby on Rails. Để có được điều này, hãy làm theo hướng dẫn của ta về Cách cài đặt Ruby on Rails với rbenv trên Ubuntu 18.04 hoặc Cách cài đặt Ruby on Rails với rbenv trên CentOS 7 .Nếu bạn muốn phát triển ứng dụng này trên macOS, vui lòng xem hướng dẫn này về Cách cài đặt Ruby on Rails với rbenv trên macOS . Hướng dẫn này đã được thử nghiệm trên version 2.6.3 của Ruby và version 5.2.3 của Rails, vì vậy hãy đảm bảo chỉ định các version này trong quá trình cài đặt.
Cài đặt PostgreSQL, như trong Bước 1 và 2 của hướng dẫn Cách sử dụng PostgreSQL với Ứng dụng Ruby on Rails của bạn trên Ubuntu 18.04 hoặc Cách sử dụng PostgreSQL với Ứng dụng Ruby on Rails của bạn trên macOS . Để làm theo hướng dẫn này, hãy sử dụng PostgreSQL version 10. Nếu bạn đang tìm cách phát triển ứng dụng này trên một bản phân phối Linux khác hoặc trên một hệ điều hành khác, hãy xem trang download PostgreSQL chính thức . Để biết thêm thông tin về cách sử dụng PostgreSQL, hãy xem hướng dẫn Cách cài đặt và sử dụng PostgreSQL của ta .
Bước 1 - Tạo một ứng dụng Rails mới
Trong bước này, bạn sẽ xây dựng ứng dụng công thức của bạn trên khung ứng dụng Rails. Đầu tiên, bạn sẽ tạo một ứng dụng Rails mới, ứng dụng này sẽ được cài đặt để hoạt động với React ngay lập tức với ít cấu hình.
Rails cung cấp một số tập lệnh được gọi là trình tạo giúp tạo ra mọi thứ cần thiết để xây dựng một ứng dụng web hiện đại. Để xem danh sách đầy đủ các lệnh này và tác dụng của chúng, hãy chạy lệnh sau trong cửa sổ Terminal :
- rails -h
Điều này sẽ mang lại một danh sách đầy đủ các tùy chọn, cho phép bạn cài đặt các thông số cho ứng dụng của bạn . Một trong các lệnh được liệt kê là lệnh new
, lệnh này sẽ tạo một ứng dụng Rails mới.
Bây giờ, bạn sẽ tạo một ứng dụng Rails mới bằng cách sử dụng trình new
. Chạy lệnh sau trong cửa sổ Terminal :
- rails new rails_react_recipe -d=postgresql -T --webpack=react --skip-coffee
Lệnh trước tạo một ứng dụng Rails mới trong một folder có tên rails_react_recipe
, cài đặt các phụ thuộc Ruby và JavaScript cần thiết và cấu hình Webpack. Hãy xem qua các cờ được liên kết với lệnh trình tạo new
này:
- Cờ
-d
chỉ định công cụ database ưa thích, trong trường hợp này là PostgreSQL. - Cờ
-T
hướng dẫn Rails bỏ qua việc tạo file thử nghiệm, vì bạn sẽ không viết thử nghiệm cho các mục đích của hướng dẫn này. Lệnh này cũng được đề xuất nếu bạn muốn sử dụng một công cụ kiểm tra Ruby khác với công cụ mà Rails cung cấp. -
--webpack
hướng dẫn Rails cấu hình trước cho JavaScript bằng gói webpack , trong trường hợp này là đặc biệt cho một ứng dụng React. -
--skip-coffee
yêu cầu Rails không cài đặt CoffeeScript , điều này không cần thiết cho hướng dẫn này.
Khi lệnh chạy xong, hãy chuyển vào folder rails_react_recipe
, là folder root của ứng dụng của bạn:
- cd rails_react_recipe
Tiếp theo, liệt kê nội dung của folder :
- ls
Thư mục root này có một số file và folder được tạo tự động tạo nên cấu trúc của ứng dụng Rails, bao gồm file package.json
chứa các phần phụ thuộc cho ứng dụng React.
Đến đây bạn đã tạo thành công một ứng dụng Rails mới, bạn đã sẵn sàng kết nối nó với database trong bước tiếp theo.
Bước 2 - Cài đặt database
Trước khi chạy ứng dụng Rails mới, trước tiên bạn phải kết nối nó với database . Trong bước này, bạn sẽ kết nối ứng dụng Rails mới tạo với database PostgreSQL, vì vậy dữ liệu công thức có thể được lưu trữ và tìm nạp khi cần thiết.
Tệp database.yml
được tìm thấy trong config/database.yml
chứa các chi tiết về database như tên database cho các môi trường phát triển khác nhau. Rails chỉ định tên database cho các môi trường phát triển khác nhau bằng cách thêm dấu gạch dưới ( _
) theo sau tên môi trường vào tên ứng dụng của bạn. Bạn luôn có thể thay đổi bất kỳ tên database môi trường nào thành bất kỳ tên nào bạn muốn.
Lưu ý: Đến đây, bạn có thể thay đổi config/database.yml
để cài đặt role PostgreSQL mà bạn muốn Rails sử dụng để tạo database của bạn . Nếu bạn đã làm theo Yêu cầu Cách sử dụng PostgreSQL với Ứng dụng Ruby on Rails của bạn và tạo một role được bảo mật bằng password , bạn có thể làm theo hướng dẫn trong Bước 4 dành cho macOS hoặc Ubuntu 18.04 .
Như đã nói trước đó, Rails cung cấp rất nhiều lệnh để giúp việc phát triển các ứng dụng web trở nên dễ dàng. Điều này bao gồm các lệnh để làm việc với database , chẳng hạn như create
, drop
và reset
. Để tạo database cho ứng dụng của bạn, hãy chạy lệnh sau trong cửa sổ Terminal:
- rails db:create
Lệnh này tạo một database development
và test
, tạo ra kết quả sau:
OutputCreated database 'rails_react_recipe_development' Created database 'rails_react_recipe_test'
Bây giờ ứng dụng đã được kết nối với database , hãy khởi động ứng dụng bằng cách chạy lệnh sau trong cửa sổ Terminal:
- rails s --binding=127.0.0.1
Lệnh s
hoặc server
kích hoạt Puma , là web server được phân phối với Rails theo mặc định và --binding=127.0.0.1
liên kết server với server localhost
của bạn.
Khi bạn chạy lệnh này, dấu nhắc lệnh của bạn sẽ không xuất hiện và bạn sẽ thấy kết quả sau:
Output=> Booting Puma => Rails 5.2.3 application starting in development => Run `rails server -h` for more startup options Puma starting in single mode... * Version 3.12.1 (ruby 2.6.3-p62), codename: Llamas in Pajamas * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://127.0.0.1:3000 Use Ctrl-C to stop
Để xem ứng dụng của bạn, hãy mở cửa sổ trình duyệt và chuyển đến http://localhost:3000
. Bạn sẽ thấy trang chào mừng mặc định của Rails:
Điều này nghĩa là bạn đã cài đặt đúng ứng dụng Rails của bạn .
Để dừng web server bất kỳ lúc nào, hãy nhấn CTRL+C
trong cửa sổ Đầu cuối nơi server đang chạy. Hãy tiếp tục và làm điều này ngay bây giờ; bạn sẽ nhận được một tin nhắn tạm biệt từ Puma:
Output^C- Gracefully stopping, waiting for requests to finish === puma shutdown: 2019-07-31 14:21:24 -0400 === - Goodbye! Exiting
Dấu nhắc của bạn sau đó sẽ xuất hiện lại.
Bạn đã cài đặt thành công database cho ứng dụng công thức nấu ăn của bạn . Trong bước tiếp theo, bạn sẽ cài đặt tất cả các phụ thuộc JavaScript bổ sung mà bạn cần để kết hợp với giao diện user React của bạn .
Bước 3 - Cài đặt phụ thuộc giao diện user
Trong bước này, bạn sẽ cài đặt các phụ thuộc JavaScript cần thiết trên giao diện user của ứng dụng công thức nấu ăn của bạn. Chúng bao gồm:
- Bộ định tuyến React , để xử lý chuyển trong ứng dụng React.
- Bootstrap , để tạo kiểu cho các thành phần front-end của bạn.
- jQuery và Popper , để làm việc với Bootstrap.
Chạy lệnh sau trong cửa sổ Terminal để cài đặt các gói này với trình quản lý gói Yarn:
- yarn add react-router-dom bootstrap jquery popper.js
Lệnh này sử dụng Yarn để cài đặt các gói được chỉ định và thêm chúng vào file package.json
. Để xác minh điều này, hãy xem file package.json
nằm trong folder root của dự án:
- nano package.json
Bạn sẽ thấy các gói đã cài đặt được liệt kê trong khóa dependencies
:
{ "name": "rails_react_recipe", "private": true, "dependencies": { "@babel/preset-react": "^7.0.0", "@rails/webpacker": "^4.0.7", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", "bootstrap": "^4.3.1", "jquery": "^3.4.1", "popper.js": "^1.15.0", "prop-types": "^15.7.2", "react": "^16.8.6", "react-dom": "^16.8.6", "react-router-dom": "^5.0.1" }, "devDependencies": { "webpack-dev-server": "^3.7.2" } }
Bạn đã cài đặt một số phụ thuộc front-end cho ứng dụng của bạn . Tiếp theo, bạn sẽ cài đặt một trang chủ cho ứng dụng công thức nấu ăn của bạn .
Bước 4 - Cài đặt Trang chủ
Với tất cả các phụ thuộc đã được cài đặt, trong bước này, bạn sẽ tạo một trang chủ cho ứng dụng. Trang chủ sẽ đóng role là trang đích khi user truy cập ứng dụng lần đầu tiên.
Rails tuân theo mô hình kiến trúc Model-View-Controller cho các ứng dụng. Trong mẫu MVC, mục đích của bộ điều khiển là nhận các yêu cầu cụ thể và chuyển chúng đến mô hình hoặc chế độ xem thích hợp. Ngay bây giờ ứng dụng sẽ hiển thị trang chào mừng Rails khi URL root được tải trong trình duyệt. Để thay đổi điều này, bạn sẽ tạo một bộ điều khiển và chế độ xem cho trang chủ và khớp nó với một tuyến đường.
Rails cung cấp trình tạo controller
để tạo bộ điều khiển. Bộ tạo controller
nhận được tên bộ điều khiển, cùng với một hành động phù hợp. Để biết thêm về điều này, hãy xem tài liệu chính thức của Rails .
Hướng dẫn này sẽ gọi Homepage
bộ điều khiển. Chạy lệnh sau trong cửa sổ Terminal để tạo bộ điều khiển Trang chủ với hành động index
.
- rails g controller Homepage index
Ghi chú:
Trên Linux, nếu bạn gặp lỗi FATAL: Listen error: unable to monitor directories for changes.
, điều này là do giới hạn hệ thống về số lượng file mà máy của bạn có thể theo dõi các thay đổi. Chạy lệnh sau để sửa nó:
- echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
Điều này sẽ làm tăng vĩnh viễn số lượng folder mà bạn có thể theo dõi với Listen
524288
. Bạn có thể thay đổi lại điều này bằng cách chạy lệnh tương tự và thay 524288
bằng số bạn muốn.
Chạy lệnh này sẽ tạo ra các file sau:
- Tệp
homepage_controller.rb
để nhận tất cả các yêu cầu liên quan đến trang chủ. Tệp này chứa hành độngindex
mà bạn đã chỉ định trong lệnh. - Tệp
homepage.js
để thêm bất kỳ hành vi JavaScript nào liên quan đến bộ điều khiểnHomepage
. - Tệp
homepage.scss
để thêm các kiểu liên quan đến bộ điều khiểnHomepage
. - Tệp
homepage_helper.rb
để thêm các phương thức trợ giúp liên quan đến bộ điều khiểnHomepage
. - Tệp
index.html.erb
là trang xem để hiển thị bất kỳ thứ gì liên quan đến trang chủ.
Ngoài các trang mới này được tạo bằng cách chạy lệnh Rails, Rails cũng cập nhật file định tuyến của bạn có tại config/routes.rb
. Nó thêm một tuyến get
cho trang chủ của bạn mà bạn sẽ sửa đổi làm tuyến root của bạn .
Một tuyến root trong Rails chỉ định những gì sẽ hiển thị khi user truy cập vào URL root của ứng dụng của bạn. Trong trường hợp này, bạn muốn user xem trang chủ của bạn . Mở file định tuyến có tại config/routes.rb
trong trình soạn thảo yêu thích của bạn:
- nano config/routes.rb
Bên trong file này, thay thế get 'homepage/index'
bằng root 'homepage#index'
để file trông giống như sau:
Rails.application.routes.draw do root 'homepage#index' # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html end
Sửa đổi này hướng dẫn Rails ánh xạ các yêu cầu tới folder root của ứng dụng tới hành động index
của Bộ điều khiển Homepage
, từ đó hiển thị bất kỳ thứ gì có trong index.html.erb
tại app/views/homepage/index.html.erb
trên vào trình duyệt.
Để xác minh điều này đang hoạt động, hãy khởi động ứng dụng của bạn:
- rails s --binding=127.0.0.1
Mở ứng dụng trong trình duyệt, bạn sẽ thấy một trang đích mới cho ứng dụng của bạn :
Khi bạn đã xác minh ứng dụng của bạn đang hoạt động, hãy nhấn CTRL+C
để dừng server .
Tiếp theo, mở file ~/rails_react_recipe/app/views/homepage/index.html.erb
, xóa mã bên trong file , sau đó lưu file dưới dạng trống. Bằng cách này, bạn sẽ đảm bảo nội dung của index.html.erb
không can thiệp vào việc hiển thị React của giao diện user của bạn.
Đến đây bạn đã cài đặt trang chủ cho ứng dụng của bạn , bạn có thể chuyển sang phần tiếp theo, nơi bạn sẽ cấu hình giao diện user của ứng dụng để sử dụng React.
Bước 5 - Cấu hình React làm Giao diện user Rails của bạn
Trong bước này, bạn sẽ cấu hình Rails để sử dụng React trên giao diện user của ứng dụng, thay vì công cụ mẫu của nó. Điều này sẽ cho phép bạn tận dụng khả năng hiển thị của React để tạo ra một trang chủ trực quan hấp dẫn hơn.
Rails, với sự trợ giúp của đá quý Webpacker , gói tất cả mã JavaScript của bạn thành các gói . Bạn có thể tìm thấy chúng trong folder gói tại app/javascript/packs
. Bạn có thể liên kết các gói này trong dạng xem Rails bằng trình trợ giúp javascript_pack_tag
và bạn có thể liên kết các bảng định kiểu được nhập vào các gói bằng trình trợ giúp stylesheet_pack_tag
. Để tạo một điểm vào môi trường React của bạn, bạn sẽ thêm một trong các gói này vào bố cục ứng dụng của bạn .
Đầu tiên, đổi tên ~/rails_react_recipe/app/javascript/packs/hello_react.jsx
thành ~/rails_react_recipe/app/javascript/packs/Index.jsx
.
- mv ~/rails_react_recipe/app/javascript/packs/hello_react.jsx ~/rails_react_recipe/app/javascript/packs/Index.jsx
Sau khi đổi tên file , hãy mở application.html.erb
, file bố cục ứng dụng:
- nano ~/rails_react_recipe/app/views/layouts/application.html.erb
Thêm các dòng mã được đánh dấu sau vào cuối thẻ head trong file bố cục ứng dụng:
<!DOCTYPE html> <html> <head> <title>RailsReactRecipe</title> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <%= javascript_pack_tag 'Index' %> </head> <body> <%= yield %> </body> </html>
Thêm gói JavaScript vào tiêu đề ứng dụng của bạn làm cho tất cả mã JavaScript của bạn có và thực thi mã trong file Index.jsx
của bạn trên trang khi nào bạn chạy ứng dụng. Cùng với gói JavaScript, bạn cũng đã thêm thẻ meta
viewport
để kiểm soát kích thước và tỷ lệ của các trang trên ứng dụng của bạn .
Lưu và thoát khỏi file .
Bây giờ file mục nhập của bạn đã được tải lên trang, hãy tạo một thành phần React cho trang chủ của bạn. Bắt đầu bằng cách tạo folder components
trong folder app/javascript
:
- mkdir ~/rails_react_recipe/app/javascript/components
Thư mục components
sẽ chứa thành phần cho trang chủ, cùng với các thành phần React khác trong ứng dụng. Trang chủ sẽ chứa một số văn bản và nút kêu gọi hành động để xem tất cả các công thức nấu ăn.
Trong editor , hãy tạo file Home.jsx
trong folder components
:
- nano ~/rails_react_recipe/app/javascript/components/Home.jsx
Thêm mã sau vào file :
import React from "react"; import { Link } from "react-router-dom"; export default () => ( <div className="vw-100 vh-100 primary-color d-flex align-items-center justify-content-center"> <div className="jumbotron jumbotron-fluid bg-transparent"> <div className="container secondary-color"> <h1 className="display-4">Food Recipes</h1> <p className="lead"> A curated list of recipes for the best homemade meal and delicacies. </p> <hr className="my-4" /> <Link to="/recipes" className="btn btn-lg custom-button" role="button" > View Recipes </Link> </div> </div> </div> );
Trong đoạn mã này, bạn đã nhập React và cả thành phần Link
từ Bộ định tuyến React. Thành phần Link
tạo ra một siêu kết nối để chuyển từ trang này sang trang khác. Sau đó, bạn đã tạo và xuất một thành phần chức năng chứa một số ngôn ngữ Đánh dấu cho trang chủ của bạn , được tạo kiểu với các lớp Bootstrap.
Với thành phần Home
của bạn tại chỗ, bây giờ bạn sẽ cài đặt định tuyến bằng React Router. Tạo folder định routes
trong folder app/javascript
:
- mkdir ~/rails_react_recipe/app/javascript/routes
Thư mục routes
sẽ chứa một vài tuyến với các thành phần tương ứng của chúng. Khi nào bất kỳ tuyến đường cụ thể nào được tải, nó sẽ hiển thị thành phần tương ứng của nó cho trình duyệt.
Trong folder định routes
, hãy tạo file Index.jsx
:
- nano ~/rails_react_recipe/app/javascript/routes/Index.jsx
Thêm mã sau vào 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> );
Trong file định tuyến Index.jsx
này, bạn đã nhập một số module : module React
cho phép ta sử dụng React và các module BrowserRouter
, Route
và Switch
từ React Router, cùng nhau giúp ta chuyển từ tuyến đường này sang tuyến đường khác. Cuối cùng, bạn đã nhập thành phần Home
của bạn , thành phần này sẽ được hiển thị khi nào một yêu cầu trùng với tuyến root ( /
). Khi nào bạn muốn thêm nhiều trang hơn vào ứng dụng của bạn , tất cả những gì bạn cần làm là khai báo một tuyến trong file này và khớp nó với thành phần bạn muốn hiển thị cho trang đó.
Lưu và thoát khỏi file .
Đến đây bạn đã cài đặt thành công định tuyến bằng React Router. Để React nhận biết được các tuyến có sẵn và sử dụng chúng, các tuyến phải có sẵn tại điểm vào ứng dụng. Để làm điều này, bạn sẽ hiển thị các tuyến của bạn trong một thành phần mà React sẽ hiển thị trong file mục nhập của bạn.
Tạo file App.jsx
trong folder app/javascript/components
:
- nano ~/rails_react_recipe/app/javascript/components/App.jsx
Thêm mã sau vào file App.jsx
:
import React from "react"; import Routes from "../routes/Index"; export default props => <>{Routes}</>;
Trong file App.jsx
, bạn đã nhập React và các file định tuyến bạn vừa tạo. Sau đó, bạn đã xuất một thành phần hiển thị các tuyến đường trong các đoạn . Thành phần này sẽ được hiển thị tại điểm đầu vào của ứng dụng, do đó làm cho các tuyến có sẵn khi nào ứng dụng được tải.
Đến đây bạn đã cài đặt App.jsx
, đã đến lúc hiển thị nó trong file mục nhập của bạn. Mở file Index.jsx
mục nhập:
- nano ~/rails_react_recipe/app/javascript/packs/Index.jsx
Thay thế mã ở đó bằng mã sau:
import React from "react"; import { render } from "react-dom"; import 'bootstrap/dist/css/bootstrap.min.css'; import $ from 'jquery'; import Popper from 'popper.js'; import 'bootstrap/dist/js/bootstrap.bundle.min'; import App from "../components/App"; document.addEventListener("DOMContentLoaded", () => { render( <App />, document.body.appendChild(document.createElement("div")) ); });
Trong đoạn mã này, bạn đã nhập React, phương thức kết xuất từ ReactDOM, Bootstrap, jQuery, Popper.js và thành phần App
của bạn. Bằng cách sử dụng phương thức kết xuất của ReactDOM, bạn đã kết xuất thành phần App
của bạn trong một phần tử div
, phần tử này đã được nối vào phần nội dung của trang. Khi nào ứng dụng được tải, React sẽ hiển thị nội dung của thành phần App
bên trong phần tử div
trên trang.
Lưu và thoát khỏi file .
Cuối cùng, thêm một số kiểu CSS vào trang chủ của bạn.
Mở application.css
của bạn trong folder ~/rails_react_recipe/app/assets/stylesheets
:
- nano ~/rails_react_recipe/app/assets/stylesheets/application.css
Tiếp theo, thay thế nội dung của file application.css
bằng mã sau:
.bg_primary-color { background-color: #FFFFFF; } .primary-color { background-color: #FFFFFF; } .bg_secondary-color { background-color: #293241; } .secondary-color { color: #293241; } .custom-button.btn { background-color: #293241; color: #FFF; border: none; } .custom-button.btn:hover { color: #FFF !important; border: none; } .hero { width: 100vw; height: 50vh; } .hero img { object-fit: cover; object-position: top; height: 100%; width: 100%; } .overlay { height: 100%; width: 100%; opacity: 0.4; }
Điều này tạo ra khung cho hình ảnh anh hùng hoặc một biểu ngữ web lớn trên trang đầu của trang web mà bạn sẽ thêm vào sau. Ngoài ra, điều này tạo kiểu cho nút mà user sẽ sử dụng để vào ứng dụng.
Với các kiểu CSS của bạn tại chỗ, hãy lưu và thoát khỏi file . Tiếp theo, khởi động lại web server cho ứng dụng của bạn, sau đó reload ứng dụng trong trình duyệt của bạn. Bạn sẽ thấy một trang chủ hoàn toàn mới:
Trong bước này, bạn đã cấu hình ứng dụng của bạn để ứng dụng sử dụng React làm giao diện user . Trong phần tiếp theo, bạn sẽ tạo các mô hình và bộ điều khiển cho phép bạn tạo, đọc, cập nhật và xóa các công thức nấu ăn.
Bước 6 - Tạo bộ điều khiển công thức và mô hình
Đến đây bạn đã cài đặt giao diện user React cho ứng dụng của bạn , trong bước này, bạn sẽ tạo mô hình Recipe và bộ điều khiển. Mô hình công thức sẽ đại diện cho bảng database chứa thông tin về công thức của user trong khi bộ điều khiển sẽ nhận và xử lý các yêu cầu tạo, đọc, cập nhật hoặc xóa công thức nấu ăn. Khi user yêu cầu một công thức, bộ điều khiển công thức sẽ nhận yêu cầu này và chuyển nó đến mô hình công thức, mô hình này sẽ lấy dữ liệu được yêu cầu từ database . Sau đó, mô hình trả về dữ liệu công thức làm phản hồi cho bộ điều khiển. Cuối cùng, thông tin này được hiển thị trên trình duyệt.
Bắt đầu bằng cách tạo mô hình Công thức bằng cách sử dụng lệnh con generate model
do Rails cung cấp và bằng cách chỉ định tên của mô hình cùng với các cột và kiểu dữ liệu của nó. Chạy lệnh sau trong cửa sổ Terminal để tạo mô hình Recipe
:
- rails generate model Recipe name:string ingredients:text instruction:text image:string
Lệnh trước hướng dẫn Rails tạo một mô hình Recipe
cùng với một cột name
của loại string
, một ingredients
và cột instruction
của text
loại, và một cột image
của loại string
. Hướng dẫn này đã đặt tên cho mô hình là Recipe
, bởi vì theo quy ước, các mô hình trong Rails sử dụng tên số ít trong khi các bảng database tương ứng của chúng sử dụng tên số nhiều.
Chạy lệnh generate model
tạo ra hai file :
- Một file công
recipe.rb
chứa tất cả logic liên quan đến mô hình. -
20190407161357 _create_recipes.rb
(số ở đầu file có thể khác nhau tùy thuộc vào ngày bạn chạy lệnh). Đây là file di chuyển chứa hướng dẫn tạo cấu trúc database .
Tiếp theo, chỉnh sửa file mô hình công thức đảm bảo rằng chỉ dữ liệu hợp lệ mới được lưu vào database . Bạn có thể đạt được điều này bằng cách thêm một số xác nhận database vào mô hình của bạn . Mở mô hình công thức của bạn tại app/models/recipe.rb
:
- nano ~/rails_react_recipe/app/models/recipe.rb
Thêm các dòng mã được đánh dấu sau vào file :
class Recipe < ApplicationRecord validates :name, presence: true validates :ingredients, presence: true validates :instruction, presence: true end
Trong mã này, bạn đã thêm xác thực mô hình để kiểm tra sự hiện diện của name
, ingredients
và trường instruction
. Nếu không có sự hiện diện của ba trường này, một công thức không hợp lệ và sẽ không được lưu vào database .
Lưu và thoát khỏi file .
Để Rails tạo bảng recipes
trong database của bạn, bạn phải chạy một quá trình di chuyển , trong Rails là một cách để áp dụng các thay đổi đối với database của bạn theo chương trình. Để chắc chắn rằng sự di cư làm việc với database bạn đã cài đặt , nó là cần thiết để thay đổi các 20190407161357 _create_recipes.rb
file .
Mở file này trong editor :
- nano ~/rails_react_recipe/db/migrate/20190407161357_create_recipes.rb
Thêm các dòng được đánh dấu sau để file trông giống như sau:
class CreateRecipes < ActiveRecord::Migration[5.2] def change create_table :recipes do |t| t.string :name, null: false t.text :ingredients, null: false t.text :instruction, null: false t.string :image, default: 'https://raw.githubusercontent.com/do-community/react_rails_recipe/master/app/assets/images/Sammy_Meal.jpg' t.timestamps end end end
Tệp di chuyển này chứa một lớp Ruby với một phương thức change
và một lệnh để tạo một bảng được gọi là recipes
cùng với các cột và kiểu dữ liệu của chúng. Bạn cũng đã cập nhật 20190407161357 _create_recipes.rb
với ràng buộc NOT NULL
20190407161357 _create_recipes.rb
đối với name
, ingredients
và cột instruction
bằng cách thêm null: false
, đảm bảo các cột này có giá trị trước khi thay đổi database . Cuối cùng, bạn đã thêm một URL hình ảnh mặc định cho cột hình ảnh của bạn ; đây có thể là một URL khác nếu bạn muốn sử dụng một hình ảnh khác.
Với những thay đổi này, hãy lưu và thoát khỏi file . Đến đây bạn đã sẵn sàng để chạy quá trình di chuyển và tạo bảng của bạn . Trong cửa sổ Terminal , hãy chạy lệnh sau:
- rails db:migrate
Ở đây bạn đã sử dụng lệnh di chuyển database , lệnh này thực thi các hướng dẫn trong file di chuyển của bạn. Khi lệnh chạy thành công, bạn sẽ nhận được kết quả tương tự như sau:
Output== 20190407161357 CreateRecipes: migrating ==================================== -- create_table(:recipes) -> 0.0140s == 20190407161357 CreateRecipes: migrated (0.0141s) ===========================
Với mô hình công thức của bạn tại chỗ, hãy tạo bộ điều khiển công thức của bạn và thêm logic để tạo, đọc và xóa công thức. Trong cửa sổ Terminal , hãy chạy lệnh sau:
- rails generate controller api/v1/Recipes index create show destroy -j=false -y=false --skip-template-engine --no-helper
Trong lệnh này, bạn đã tạo bộ điều khiển Recipes
trong folder api/v1
với index
, create
, show
và destroy
hành động. Hành động index
sẽ xử lý việc tìm nạp tất cả các công thức nấu ăn của bạn, hành động create
sẽ chịu trách nhiệm tạo công thức nấu ăn mới, hành động show
sẽ tìm nạp một công thức duy nhất và hành động destroy
sẽ giữ logic để xóa một công thức.
Bạn cũng đã thông qua một số cờ để làm cho bộ điều khiển nhẹ hơn, bao gồm:
-
-j=false
hướng dẫn Rails bỏ qua việc tạo các file JavaScript được liên kết. -
-y=false
hướng dẫn Rails bỏ qua việc tạo các file biểu định kiểu được liên kết. -
--skip-template-engine
, hướng dẫn Rails bỏ qua việc tạo các file dạng xem Rails, vì React đang xử lý các nhu cầu front-end của bạn. -
--no-helper
, hướng dẫn Rails bỏ qua việc tạo file trợ giúp cho bộ điều khiển của bạn.
Chạy lệnh cũng cập nhật file tuyến đường của bạn với tuyến đường cho mỗi hành động trong bộ điều khiển Recipes
. Để sử dụng các tuyến này, hãy áp dụng các thay đổi đối với file config/routes.rb
của bạn.
Mở file tuyến đường trong editor của bạn:
- nano ~/rails_react_recipe/config/routes.rb
Khi nó được mở, hãy cập nhật nó để trông giống như mã sau, thay đổi hoặc thêm các dòng được đánh dấu:
Rails.application.routes.draw do namespace :api do namespace :v1 do get 'recipes/index' post 'recipes/create' get '/show/:id', to: 'recipes#show' delete '/destroy/:id', to: 'recipes#destroy' end end root 'homepage#index' get '/*path' => 'homepage#index' # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html end
Trong file tuyến đường này, bạn đã sửa đổi động từ HTTP của tuyến đường create
và destroy
để nó có thể post
và delete
dữ liệu. Bạn cũng đã sửa đổi các tuyến cho hành động show
và destroy
bằng cách thêm tham số :id
vào tuyến. :id
sẽ giữ số nhận dạng của công thức bạn muốn đọc hoặc xóa.
Bạn cũng đã thêm một tuyến bắt tất cả với get '/*path'
sẽ hướng bất kỳ yêu cầu nào khác không trùng với các tuyến hiện có tới hành động index
của bộ điều khiển homepage
. Bằng cách này, định tuyến trên giao diện user sẽ xử lý các yêu cầu không liên quan đến việc tạo, đọc hoặc xóa công thức nấu ăn.
Lưu và thoát khỏi file .
Để xem danh sách các tuyến có sẵn trong ứng dụng của bạn, hãy chạy lệnh sau trong cửa sổ Terminal :
- rails routes
Chạy lệnh này sẽ hiển thị danh sách các mẫu URI, động từ và bộ điều khiển hoặc hành động phù hợp cho dự án của bạn.
Tiếp theo, thêm logic để nhận tất cả các công thức cùng một lúc. Rails sử dụng thư viện ActiveRecord để xử lý các việc liên quan đến database như thế này. ActiveRecord kết nối các lớp với các bảng database quan hệ và cung cấp một API phong phú để làm việc với chúng.
Để lấy tất cả các công thức, bạn sẽ sử dụng ActiveRecord để truy vấn bảng công thức và tìm nạp tất cả các công thức tồn tại trong database .
Mở file recipes_controller.rb
bằng lệnh sau:
- nano ~/rails_react_recipe/app/controllers/api/v1/recipes_controller.rb
Thêm các dòng mã được đánh dấu sau vào bộ điều khiển công thức nấu ăn:
class Api::V1::RecipesController < ApplicationController def index recipe = Recipe.all.order(created_at: :desc) render json: recipe end def create end def show end def destroy end end
Trong hành động index
của bạn, sử dụng all
phương pháp được cung cấp bởi ActiveRecord, bạn sẽ có được tất cả các công thức trong database của bạn . Sử dụng phương pháp order
, bạn sắp xếp chúng theo thứ tự giảm dần theo ngày tạo của chúng. Bằng cách này, trước tiên bạn có những công thức nấu ăn mới nhất. Cuối cùng, bạn gửi danh sách các công thức nấu ăn của bạn dưới dạng phản hồi JSON với render
.
Tiếp theo, thêm logic để tạo công thức nấu ăn mới. Như với việc tìm nạp tất cả các công thức nấu ăn, bạn sẽ dựa vào ActiveRecord để xác thực và lưu các chi tiết công thức được cung cấp. Cập nhật bộ điều khiển công thức của bạn bằng các dòng mã được đánh dấu sau:
class Api::V1::RecipesController < ApplicationController def index recipe = Recipe.all.order(created_at: :desc) render json: recipe end def create recipe = Recipe.create!(recipe_params) if recipe render json: recipe else render json: recipe.errors end end def show end def destroy end private def recipe_params params.permit(:name, :image, :ingredients, :instruction) end end
Trong hành động create
, bạn sử dụng phương thức create
của ActiveRecord để tạo một công thức mới. Phương thức create
có khả năng gán tất cả các tham số bộ điều khiển được cung cấp vào mô hình cùng một lúc. Điều này giúp dễ dàng tạo profile , nhưng cũng mở ra khả năng bị sử dụng độc hại. Điều này có thể được ngăn chặn bằng cách sử dụng một tính năng được cung cấp bởi Rails được gọi là tham số mạnh . Bằng cách này, không thể chỉ định các thông số trừ khi chúng được đưa vào danh sách trắng. Trong mã của bạn, bạn đã chuyển một tham số recipe_params
cho phương thức create
. Công recipe_params
là một phương thức private
trong đó bạn đưa các tham số bộ điều khiển vào danh sách trắng để ngăn nội dung sai hoặc độc hại xâm nhập vào database của bạn. Trong trường hợp này, bạn đang cho phép name
, image
, ingredients
và tham số instruction
để sử dụng hợp lệ phương thức create
.
Bộ điều khiển công thức của bạn hiện có thể đọc và tạo công thức nấu ăn. Tất cả những gì còn lại là logic để đọc và xóa một công thức duy nhất. Cập nhật bộ điều khiển công thức nấu ăn của bạn bằng mã sau:
class Api::V1::RecipesController < ApplicationController def index recipe = Recipe.all.order(created_at: :desc) render json: recipe end def create recipe = Recipe.create!(recipe_params) if recipe render json: recipe else render json: recipe.errors end end def show if recipe render json: recipe else render json: recipe.errors end end def destroy recipe&.destroy render json: { message: 'Recipe deleted!' } end private def recipe_params params.permit(:name, :image, :ingredients, :instruction) end def recipe @recipe ||= Recipe.find(params[:id]) end end
Trong các dòng mã mới, bạn đã tạo một phương pháp recipe
riêng. Phương recipe
thức sử dụng phương thức find
của ActiveRecord để tìm một công thức có id
trùng với id
được cung cấp trong params
và gán nó cho một biến thể hiện @recipe
. Trong show
hành động, bạn kiểm tra nếu một công thức sẽ được trả về bởi các recipe
phương pháp và gửi nó như một phản ứng JSON, hoặc gửi một lỗi nếu nó không được.
Trong hành động destroy
, bạn đã làm điều gì đó tương tự bằng cách sử dụng toán tử chuyển an toàn &.
, tránh lỗi nil
khi gọi một phương thức. Điều này cho phép bạn xóa công thức chỉ khi nó tồn tại, sau đó gửi tin nhắn dưới dạng phản hồi.
Đến đây bạn đã hoàn tất việc áp dụng các thay đổi này đối với recipes_controller.rb
, hãy lưu file và thoát khỏi editor của bạn.
Trong bước này, bạn đã tạo một mô hình và bộ điều khiển cho công thức nấu ăn của bạn . Bạn đã viết tất cả logic cần thiết để làm việc với các công thức nấu ăn trên phần backend . Trong phần tiếp theo, bạn sẽ tạo các thành phần để xem công thức nấu ăn của bạn .
Bước 7 - Xem công thức nấu ăn
Trong phần này, bạn sẽ tạo các thành phần để xem công thức nấu ăn. Trước tiên, bạn sẽ tạo một trang nơi bạn có thể xem tất cả các công thức nấu ăn hiện có, sau đó là một trang khác để xem các công thức nấu ăn riêng lẻ.
Bạn sẽ bắt đầu bằng cách tạo một trang để xem tất cả các công thức nấu ăn. Tuy nhiên, trước khi bạn có thể làm điều này, bạn cần có các công thức nấu ăn, vì database của bạn hiện đang trống. Rails cho ta cơ hội tạo dữ liệu root cho ứng dụng của bạn.
Mở file hạt giống seeds.rb
để chỉnh sửa:
- nano ~/rails_react_recipe/db/seeds.rb
Thay thế nội dung của file hạt giống này bằng mã sau:
9.times do |i| Recipe.create( name: "Recipe #{i + 1}", ingredients: '227g tub clotted cream, 25g butter, 1 tsp cornflour,100g parmesan, grated nutmeg, 250g fresh fettuccine or tagliatelle, snipped chives or chopped parsley to serve (optional)', instruction: 'In a medium saucepan, stir the clotted cream, butter, and cornflour over a low-ish heat and bring to a low simmer. Turn off the heat and keep warm.' ) end
Trong đoạn mã này, bạn đang sử dụng một vòng lặp để hướng dẫn Rails tạo chín công thức nấu ăn với name
, ingredients
và instruction
. Lưu và thoát khỏi file .
Để tạo database với dữ liệu này, hãy chạy lệnh sau trong cửa sổ Terminal :
- rails db:seed
Chạy lệnh này sẽ thêm chín công thức vào database của bạn. Đến đây bạn có thể tìm nạp chúng và hiển thị chúng trên giao diện user .
Thành phần để xem tất cả các công thức sẽ thực hiện một yêu cầu HTTP đối với hành động index
trong RecipesController
để nhận danh sách tất cả các công thức. Các công thức nấu ăn này sau đó sẽ được hiển thị trong các thẻ trên trang.
Tạo file Recipes.jsx
trong folder app/javascript/components
:
- nano ~/rails_react_recipe/app/javascript/components/Recipes.jsx
Khi file được mở, hãy nhập các module React và Link vào đó bằng cách thêm các dòng sau:
import React from "react"; import { Link } from "react-router-dom";
Tiếp theo, tạo một lớp Recipes
mở rộng lớp React.Component
. Thêm đoạn mã được đánh dấu sau để tạo một thành phần React mở rộng React.Component
:
import React from "react"; import { Link } from "react-router-dom"; class Recipes extends React.Component { constructor(props) { super(props); this.state = { recipes: [] }; } } export default Recipes;
Bên trong hàm tạo , ta đang khởi tạo một đối tượng trạng thái chứa trạng thái của công thức nấu ăn của bạn, khi khởi tạo là một mảng trống ( []
).
Tiếp theo, thêm một phương thức componentDidMount
trong lớp Recipe. Phương thức componentDidMount là một phương thức vòng đời React được gọi ngay sau khi một thành phần được mount . Trong phương pháp vòng đời này, bạn sẽ thực hiện một cuộc gọi để tìm nạp tất cả các công thức nấu ăn của bạn . Để làm điều này, hãy thêm các dòng sau:
import React from "react"; import { Link } from "react-router-dom"; class Recipes extends React.Component { constructor(props) { super(props); this.state = { recipes: [] }; } componentDidMount() { const url = "/api/v1/recipes/index"; fetch(url) .then(response => { if (response.ok) { return response.json(); } throw new Error("Network response was not ok."); }) .then(response => this.setState({ recipes: response })) .catch(() => this.props.history.push("/")); } } export default Recipes;
Trong phương thức componentDidMount
, bạn đã thực hiện một cuộc gọi HTTP để tìm nạp tất cả các công thức nấu ăn bằng cách sử dụng API Tìm nạp . Nếu phản hồi thành công, ứng dụng sẽ lưu mảng công thức về trạng thái công thức. Nếu có lỗi, nó sẽ chuyển hướng user đến trang chủ.
Cuối cùng, thêm một phương thức render
trong lớp Recipe
. Phương thức kết xuất chứa các phần tử React sẽ được đánh giá và hiển thị trên trang trình duyệt khi một thành phần được kết xuất. Trong trường hợp này, render
phương pháp sẽ làm cho thẻ của công thức nấu ăn từ trạng thái thành phần. Thêm các dòng được đánh dấu sau vào Recipes.jsx
:
import React from "react"; import { Link } from "react-router-dom"; class Recipes extends React.Component { constructor(props) { super(props); this.state = { recipes: [] }; } componentDidMount() { const url = "/api/v1/recipes/index"; fetch(url) .then(response => { if (response.ok) { return response.json(); } throw new Error("Network response was not ok."); }) .then(response => this.setState({ recipes: response })) .catch(() => this.props.history.push("/")); } render() { const { recipes } = this.state; const allRecipes = recipes.map((recipe, index) => ( <div key={index} className="col-md-6 col-lg-4"> <div className="card mb-4"> <img src={recipe.image} className="card-img-top" alt={`${recipe.name} image`} /> <div className="card-body"> <h5 className="card-title">{recipe.name}</h5> <Link to={`/recipe/${recipe.id}`} className="btn custom-button"> View Recipe </Link> </div> </div> </div> )); const noRecipe = ( <div className="vw-100 vh-50 d-flex align-items-center justify-content-center"> <h4> No recipes yet. Why not <Link to="/new_recipe">create one</Link> </h4> </div> ); return ( <> <section className="jumbotron jumbotron-fluid text-center"> <div className="container py-5"> <h1 className="display-4">Recipes for every occasion</h1> <p className="lead text-muted"> We’ve pulled together our most popular recipes, our latest additions, and our editor’s picks, so there’s sure to be something tempting for you to try. </p> </div> </section> <div className="py-5"> <main className="container"> <div className="text-right mb-3"> <Link to="/recipe" className="btn custom-button"> Create New Recipe </Link> </div> <div className="row"> {recipes.length > 0 ? allRecipes : noRecipe} </div> <Link to="/" className="btn btn-link"> Home </Link> </main> </div> </> ); } } export default Recipes;
Lưu và thoát khỏi Recipes.jsx
.
Đến đây bạn đã tạo một thành phần để hiển thị tất cả các công thức, bước tiếp theo là tạo một lộ trình cho nó. Mở file định tuyến front-end có tại app/javascript/routes/Index.jsx
:
- nano app/javascript/routes/Index.jsx
Thêm các dòng được đánh dấu sau vào file :
import React from "react"; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import Home from "../components/Home"; import Recipes from "../components/Recipes"; export default ( <Router> <Switch> <Route path="/" exact component={Home} /> <Route path="/recipes" exact component={Recipes} /> </Switch> </Router> );
Lưu và thoát khỏi file .
Đến đây, bạn nên xác minh mã của bạn đang hoạt động chính xác. Như bạn đã làm trước đây, hãy sử dụng lệnh sau để khởi động server của bạn:
- rails s --binding=127.0.0.1
Hãy tiếp tục và mở ứng dụng trong trình duyệt của bạn. Bằng cách nhấp vào nút View Recipe trên trang chủ, bạn sẽ thấy một màn hình hiển thị các công thức hạt giống của bạn :
Sử dụng CTRL+C
trong cửa sổ Terminal để dừng server và nhận lại dấu nhắc của bạn.
Đến đây bạn có thể xem tất cả các công thức nấu ăn tồn tại trong ứng dụng của bạn , đã đến lúc tạo thành phần thứ hai để xem các công thức nấu ăn riêng lẻ. Tạo file Recipe.jsx
trong folder app/javascript/components
:
- nano app/javascript/components/Recipe.jsx
Như với thành phần Recipes
, nhập các module React và Link bằng cách thêm các dòng sau:
import React from "react"; import { Link } from "react-router-dom";
Tiếp theo, tạo một lớp Recipe
mở rộng lớp React.Component
bằng cách thêm các dòng mã được đánh dấu:
import React from "react"; import { Link } from "react-router-dom"; class Recipe extends React.Component { constructor(props) { super(props); this.state = { recipe: { ingredients: "" } }; this.addHtmlEntities = this.addHtmlEntities.bind(this); } } export default Recipe;
Giống như với thành phần Recipes
, trong hàm tạo, bạn khởi tạo một đối tượng trạng thái chứa trạng thái của một công thức. Bạn cũng ràng buộc một phương thức addHtmlEntities
với this
để nó có thể được truy cập trong thành phần. Phương thức addHtmlEntities
sẽ được sử dụng để thay thế các thực thể ký tự bằng các thực thể HTML trong thành phần.
Để tìm một công thức cụ thể, ứng dụng của bạn cần id
của công thức đó. Điều này nghĩa là thành phần Recipe
của bạn mong đợi một param
id
. Bạn có thể truy cập điều này thông qua các props
được chuyển vào thành phần.
Tiếp theo, thêm một componentDidMount
phương pháp mà bạn sẽ truy cập vào id
param
từ match
then chốt của props
đối tượng. Sau khi nhận được id
, bạn sẽ thực hiện một yêu cầu HTTP để tìm nạp công thức. Thêm các dòng được đánh dấu sau vào file của bạn:
import React from "react"; import { Link } from "react-router-dom"; class Recipe extends React.Component { constructor(props) { super(props); this.state = { recipe: { ingredients: "" } }; this.addHtmlEntities = this.addHtmlEntities.bind(this); } componentDidMount() { const { match: { params: { id } } } = this.props; const url = `/api/v1/show/${id}`; fetch(url) .then(response => { if (response.ok) { return response.json(); } throw new Error("Network response was not ok."); }) .then(response => this.setState({ recipe: response })) .catch(() => this.props.history.push("/recipes")); } } export default Recipe;
Trong componentDidMount
phương pháp, sử dụng đối tượng destructuring , bạn sẽ có được id
param
từ props
đối tượng, sau đó sử dụng Fetch API, bạn thực hiện một yêu cầu HTTP để lấy công thức sở hữu id
và tiết kiệm nó sang trạng thái thành phần sử dụng setState
phương pháp. Nếu công thức không tồn tại, ứng dụng sẽ chuyển hướng user đến trang công thức nấu ăn.
Bây giờ, hãy thêm phương thức addHtmlEntities
, phương thức này nhận một chuỗi và thay thế tất cả các dấu ngoặc mở và đóng đã thoát bằng các thực thể HTML của chúng. Điều này sẽ giúp ta chuyển đổi bất kỳ ký tự thoát nào đã được lưu trong hướng dẫn công thức của bạn:
import React from "react"; import { Link } from "react-router-dom"; class Recipe extends React.Component { constructor(props) { super(props); this.state = { recipe: { ingredients: "" } }; this.addHtmlEntities = this.addHtmlEntities.bind(this); } componentDidMount() { const { match: { params: { id } } } = this.props; const url = `/api/v1/show/${id}`; fetch(url) .then(response => { if (response.ok) { return response.json(); } throw new Error("Network response was not ok."); }) .then(response => this.setState({ recipe: response })) .catch(() => this.props.history.push("/recipes")); } addHtmlEntities(str) { return String(str) .replace(/</g, "<") .replace(/>/g, ">"); } } export default Recipe;
Cuối cùng, thêm một render
phương pháp mà được các công thức từ nhà nước và làm cho nó trên trang. Để thực hiện việc này, hãy thêm các dòng được đánh dấu sau:
import React from "react"; import { Link } from "react-router-dom"; class Recipe extends React.Component { constructor(props) { super(props); this.state = { recipe: { ingredients: "" } }; this.addHtmlEntities = this.addHtmlEntities.bind(this); } componentDidMount() { const { match: { params: { id } } } = this.props; const url = `/api/v1/show/${id}`; fetch(url) .then(response => { if (response.ok) { return response.json(); } throw new Error("Network response was not ok."); }) .then(response => this.setState({ recipe: response })) .catch(() => this.props.history.push("/recipes")); } addHtmlEntities(str) { return String(str) .replace(/</g, "<") .replace(/>/g, ">"); } render() { const { recipe } = this.state; let ingredientList = "No ingredients available"; if (recipe.ingredients.length > 0) { ingredientList = recipe.ingredients .split(",") .map((ingredient, index) => ( <li key={index} className="list-group-item"> {ingredient} </li> )); } const recipeInstruction = this.addHtmlEntities(recipe.instruction); return ( <div className=""> <div className="hero position-relative d-flex align-items-center justify-content-center"> <img src={recipe.image} alt={`${recipe.name} image`} className="img-fluid position-absolute" /> <div className="overlay bg-dark position-absolute" /> <h1 className="display-4 position-relative text-white"> {recipe.name} </h1> </div> <div className="container py-5"> <div className="row"> <div className="col-sm-12 col-lg-3"> <ul className="list-group"> <h5 className="mb-2">Ingredients</h5> {ingredientList} </ul> </div> <div className="col-sm-12 col-lg-7"> <h5 className="mb-2">Preparation Instructions</h5> <div dangerouslySetInnerHTML={{ __html: `${recipeInstruction}` }} /> </div> <div className="col-sm-12 col-lg-2"> <button type="button" className="btn btn-danger"> Delete Recipe </button> </div> </div> <Link to="/recipes" className="btn btn-link"> Back to recipes </Link> </div> </div> ); } } export default Recipe;
Trong phương pháp render
này, bạn chia các thành phần được phân tách bằng dấu phẩy thành một mảng và ánh xạ lên nó, tạo danh sách các thành phần. Nếu không có nguyên liệu, ứng dụng sẽ hiển thị thông báo Không có nguyên liệu nào . Nó cũng hiển thị hình ảnh công thức dưới dạng hình ảnh anh hùng, thêm nút xóa công thức bên cạnh hướng dẫn công thức và thêm nút liên kết trở lại trang công thức nấu ăn.
Lưu và thoát khỏi file .
Để xem thành phần Recipe
trên một trang, hãy thêm nó vào file tuyến đường của bạn. Mở file tuyến đường của bạn để chỉnh sửa:
- nano app/javascript/routes/Index.jsx
Bây giờ, hãy thêm các dòng được đánh dấu sau vào file :
import React from "react"; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import Home from "../components/Home"; import Recipes from "../components/Recipes"; import Recipe from "../components/Recipe"; export default ( <Router> <Switch> <Route path="/" exact component={Home} /> <Route path="/recipes" exact component={Recipes} /> <Route path="/recipe/:id" exact component={Recipe} /> </Switch> </Router> );
Trong file tuyến đường này, bạn đã nhập thành phần Recipe
của bạn và thêm một tuyến đường cho nó. Lộ trình của nó có :id
param
sẽ được thay thế bằng id
của công thức bạn muốn xem.
Sử dụng lệnh rails s
để khởi động lại server của bạn, sau đó truy cập http://localhost:3000
trong trình duyệt của bạn. Nhấp vào nút Xem công thức nấu ăn để chuyển đến trang công thức nấu ăn. Trên trang công thức nấu ăn, xem bất kỳ công thức nào bằng cách nhấp vào nút Xem công thức nấu ăn . Bạn sẽ được chào đón với một trang được điền dữ liệu từ database của bạn:
Trong phần này, bạn đã thêm chín công thức vào database của bạn và tạo các thành phần để xem các công thức này, cả riêng lẻ và dưới dạng một bộ sưu tập. Trong phần tiếp theo, bạn sẽ thêm một thành phần để tạo công thức nấu ăn.
Bước 8 - Tạo công thức nấu ăn
Bước tiếp theo để có một ứng dụng công thức thực phẩm có thể sử dụng được là khả năng tạo công thức nấu ăn mới. Trong bước này, bạn sẽ tạo một thành phần để tạo công thức nấu ăn. Thành phần này sẽ chứa một biểu mẫu để thu thập chi tiết công thức được yêu cầu từ user và sẽ thực hiện yêu cầu hành động create
trong bộ điều khiển Recipe
để lưu dữ liệu công thức.
Tạo file NewRecipe.jsx
trong folder app/javascript/components
:
- nano app/javascript/components/NewRecipe.jsx
Trong file mới, hãy nhập các module React và Liên kết bạn đã sử dụng cho đến nay trong các thành phần khác:
import React from "react"; import { Link } from "react-router-dom";
Tiếp theo, tạo một NewRecipe
lớp mà kéo dài React.Component
lớp. Thêm đoạn mã được đánh dấu sau để tạo một thành phần React mở rộng react.Component
.
import React from "react"; import { Link } from "react-router-dom"; class NewRecipe extends React.Component { constructor(props) { super(props); this.state = { name: "", ingredients: "", instruction: "" }; this.onChange = this.onChange.bind(this); this.onSubmit = this.onSubmit.bind(this); this.stripHtmlEntities = this.stripHtmlEntities.bind(this); } } export default NewRecipe;
Trong phương NewRecipe
khởi tạo của thành phần NewRecipe
, bạn đã khởi tạo đối tượng trạng thái của bạn với các trường name
, ingredients
và instruction
. Đây là những trường bạn cần để tạo một công thức hợp lệ. Bạn cũng có ba phương pháp; onChange
, onSubmit
và stripHtmlEntities
mà bạn đã ràng buộc với this
. Các phương thức này sẽ xử lý việc cập nhật trạng thái, gửi biểu mẫu và chuyển đổi các ký tự đặc biệt (như <
) thành các giá trị thoát / mã hóa của chúng (như <
), tương ứng.
Tiếp theo, tạo phương thức stripHtmlEntities
bằng cách thêm các dòng được đánh dấu vào thành phần NewRecipe
:
class NewRecipe extends React.Component { constructor(props) { super(props); this.state = { name: "", ingredients: "", instruction: "" }; this.onChange = this.onChange.bind(this); this.onSubmit = this.onSubmit.bind(this); this.stripHtmlEntities = this.stripHtmlEntities.bind(this); } stripHtmlEntities(str) { return String(str) .replace(/</g, "<") .replace(/>/g, ">"); } } export default NewRecipe;
Trong phương thức stripHtmlEntities
, bạn đang thay thế các ký tự <
và >
bằng giá trị thoát của chúng. Bằng cách này, bạn sẽ không lưu trữ HTML thô trong database của bạn .
Tiếp theo, thêm các phương thức onChange
và onSubmit
vào thành phần NewRecipe
để xử lý việc chỉnh sửa và gửi biểu mẫu:
class NewRecipe extends React.Component { constructor(props) { super(props); this.state = { name: "", ingredients: "", instruction: "" }; this.onChange = this.onChange.bind(this); this.onSubmit = this.onSubmit.bind(this); this.stripHtmlEntities = this.stripHtmlEntities.bind(this); } stripHtmlEntities(str) { return String(str) .replace(/</g, "<") .replace(/>/g, ">"); } onChange(event) { this.setState({ [event.target.name]: event.target.value }); } onSubmit(event) { event.preventDefault(); const url = "/api/v1/recipes/create"; const { name, ingredients, instruction } = this.state; if (name.length == 0 || ingredients.length == 0 || instruction.length == 0) return; const body = { name, ingredients, instruction: instruction.replace(/\n/g, "<br> <br>") }; const token = document.querySelector('meta[name="csrf-token"]').content; fetch(url, { method: "POST", headers: { "X-CSRF-Token": token, "Content-Type": "application/json" }, body: JSON.stringify(body) }) .then(response => { if (response.ok) { return response.json(); } throw new Error("Network response was not ok."); }) .then(response => this.props.history.push(`/recipe/${response.id}`)) .catch(error => console.log(error.message)); } } export default NewRecipe;
Trong phương thức onChange
, bạn đã sử dụng tên thuộc tính được tính toán của ES6 để đặt giá trị của mọi đầu vào của user thành khóa tương ứng trong trạng thái của bạn. Trong phương thức onSubmit
, bạn đã kiểm tra rằng không có đầu vào bắt buộc nào trống. Sau đó, bạn xây dựng một đối tượng có chứa các tham số được yêu cầu bởi bộ điều khiển công thức để tạo một công thức mới. Sử dụng biểu thức chính quy , bạn thay thế mọi ký tự dòng mới trong hướng dẫn bằng thẻ ngắt, vì vậy bạn có thể giữ lại định dạng văn bản do user nhập.
Để bảo vệ khỏi các cuộc tấn công truy vấn yêu cầu chéo (CSRF) , Rails đính kèm mã thông báo bảo mật CSRF vào trang HTML . Mã thông báo này được yêu cầu khi nào yêu cầu không phải GET
được thực hiện. Với hằng số token
trong mã trước đó, ứng dụng của bạn xác minh mã thông báo trên server và ném một ngoại lệ nếu mã thông báo bảo mật không trùng với những gì được mong đợi. Trong phương thức onSubmit
, ứng dụng truy xuất mã thông báo CSRF được nhúng trong trang HTML của bạn bằng Rails và thực hiện một yêu cầu HTTP với một chuỗi JSON. Nếu công thức được tạo thành công, ứng dụng sẽ chuyển hướng user đến trang công thức nơi họ có thể xem công thức mới tạo của bạn .
Cuối cùng, thêm một render
phương pháp mà làm cho một hình thức cho user nhập vào các chi tiết cho công thức user muốn tạo ra:
class NewRecipe extends React.Component { constructor(props) { super(props); this.state = { name: "", ingredients: "", instruction: "" }; this.onChange = this.onChange.bind(this); this.onSubmit = this.onSubmit.bind(this); this.stripHtmlEntities = this.stripHtmlEntities.bind(this); } stripHtmlEntities(str) { return String(str) .replace(/</g, "<") .replace(/>/g, ">"); } onChange(event) { this.setState({ [event.target.name]: event.target.value }); } onSubmit(event) { event.preventDefault(); const url = "/api/v1/recipes/create"; const { name, ingredients, instruction } = this.state; if (name.length == 0 || ingredients.length == 0 || instruction.length == 0) return; const body = { name, ingredients, instruction: instruction.replace(/\n/g, "<br> <br>") }; const token = document.querySelector('meta[name="csrf-token"]').content; fetch(url, { method: "POST", headers: { "X-CSRF-Token": token, "Content-Type": "application/json" }, body: JSON.stringify(body) }) .then(response => { if (response.ok) { return response.json(); } throw new Error("Network response was not ok."); }) .then(response => this.props.history.push(`/recipe/${response.id}`)) .catch(error => console.log(error.message)); } render() { return ( <div className="container mt-5"> <div className="row"> <div className="col-sm-12 col-lg-6 offset-lg-3"> <h1 className="font-weight-normal mb-5"> Add a new recipe to our awesome recipe collection. </h1> <form onSubmit={this.onSubmit}> <div className="form-group"> <label htmlFor="recipeName">Recipe name</label> <input type="text" name="name" id="recipeName" className="form-control" required onChange={this.onChange} /> </div> <div className="form-group"> <label htmlFor="recipeIngredients">Ingredients</label> <input type="text" name="ingredients" id="recipeIngredients" className="form-control" required onChange={this.onChange} /> <small id="ingredientsHelp" className="form-text text-muted"> Separate each ingredient with a comma. </small> </div> <label htmlFor="instruction">Preparation Instructions</label> <textarea className="form-control" id="instruction" name="instruction" rows="5" required onChange={this.onChange} /> <button type="submit" className="btn custom-button mt-3"> Create Recipe </button> <Link to="/recipes" className="btn btn-link mt-3"> Back to recipes </Link> </form> </div> </div> </div> ); } } export default NewRecipe;
Trong phương thức kết xuất, bạn có một biểu mẫu chứa ba trường đầu vào; một cho tên công recipeName
, công recipeIngredients
, người nhận và instruction
. Mỗi trường đầu vào có một trình xử lý sự kiện onChange
gọi phương thức onChange
. Ngoài ra, có một trình xử lý sự kiện onSubmit
trên nút gửi gọi phương thức onSubmit
sau đó gửi dữ liệu biểu mẫu.
Lưu và thoát khỏi file .
Để truy cập thành phần này trong trình duyệt, hãy cập nhật file tuyến của bạn với tuyến của nó:
- nano app/javascript/routes/Index.jsx
Cập nhật file tuyến đường của bạn để bao gồm các dòng được đánh dấu sau:
import React from "react"; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import Home from "../components/Home"; import Recipes from "../components/Recipes"; import Recipe from "../components/Recipe"; import NewRecipe from "../components/NewRecipe"; export default ( <Router> <Switch> <Route path="/" exact component={Home} /> <Route path="/recipes" exact component={Recipes} /> <Route path="/recipe/:id" exact component={Recipe} /> <Route path="/recipe" exact component={NewRecipe} /> </Switch> </Router> );
Với tuyến đường tại chỗ, hãy lưu và thoát file của bạn. Khởi động lại server phát triển của bạn và truy cập http://localhost:3000
trong trình duyệt của bạn. Điều hướng đến trang công thức nấu ăn và nhấp vào nút Tạo công thức mới . Bạn sẽ tìm thấy một trang có biểu mẫu để thêm công thức nấu ăn vào database của bạn :
Nhập các chi tiết công thức cần thiết và nhấp vào nút Tạo Công thức ; bạn sẽ thấy công thức mới tạo trên trang.
Trong bước này, bạn đã đưa ứng dụng công thức nấu ăn của bạn vào cuộc sống bằng cách thêm khả năng tạo công thức nấu ăn. Trong bước tiếp theo, bạn sẽ thêm chức năng xóa công thức nấu ăn.
Bước 9 - Xóa công thức nấu ăn
Trong phần này, bạn sẽ sửa đổi thành phần Công thức của bạn để có thể xóa công thức.
Khi bạn nhấp vào nút xóa trên trang công thức, ứng dụng sẽ gửi yêu cầu xóa công thức khỏi database . Để thực hiện việc này, hãy mở file Recipe.jsx
của bạn:
- nano app/javascript/components/Recipe.jsx
Trong phương thức khởi tạo của thành phần Recipe
, liên kết this
với phương thức deleteRecipe
:
class Recipe extends React.Component { constructor(props) { super(props); this.state = { recipe: { ingredients: "" } }; this.addHtmlEntities = this.addHtmlEntities.bind(this); this.deleteRecipe = this.deleteRecipe.bind(this); } ...
Bây giờ thêm phương thức deleteRecipe
vào thành phần Recipe:
class Recipe extends React.Component { constructor(props) { super(props); this.state = { recipe: { ingredients: "" } }; this.addHtmlEntities = this.addHtmlEntities.bind(this); this.deleteRecipe = this.deleteRecipe.bind(this); } componentDidMount() { const { match: { params: { id } } } = this.props; const url = `/api/v1/show/${id}`; fetch(url) .then(response => { if (response.ok) { return response.json(); } throw new Error("Network response was not ok."); }) .then(response => this.setState({ recipe: response })) .catch(() => this.props.history.push("/recipes")); } addHtmlEntities(str) { return String(str) .replace(/</g, "<") .replace(/>/g, ">"); } deleteRecipe() { const { match: { params: { id } } } = this.props; const url = `/api/v1/destroy/${id}`; const token = document.querySelector('meta[name="csrf-token"]').content; fetch(url, { method: "DELETE", headers: { "X-CSRF-Token": token, "Content-Type": "application/json" } }) .then(response => { if (response.ok) { return response.json(); } throw new Error("Network response was not ok."); }) .then(() => this.props.history.push("/recipes")) .catch(error => console.log(error.message)); } render() { const { recipe } = this.state; let ingredientList = "No ingredients available"; ...
Trong phương thức deleteRecipe
, bạn sẽ xóa id
của công thức, sau đó tạo url của bạn và lấy mã thông báo CSRF. Tiếp theo, bạn thực hiện yêu cầu DELETE
tới bộ điều khiển Recipes
để xóa công thức. Nếu công thức được xóa thành công, ứng dụng sẽ chuyển hướng user đến trang công thức nấu ăn.
Để chạy mã trong phương thức deleteRecipe
khi nào nút xóa được nhấp, hãy chuyển mã đó làm trình xử lý sự kiện nhấp vào nút. Thêm sự kiện onClick
vào nút xóa trong phương thức render
:
... return ( <div className=""> <div className="hero position-relative d-flex align-items-center justify-content-center"> <img src={recipe.image} alt={`${recipe.name} image`} className="img-fluid position-absolute" /> <div className="overlay bg-dark position-absolute" /> <h1 className="display-4 position-relative text-white"> {recipe.name} </h1> </div> <div className="container py-5"> <div className="row"> <div className="col-sm-12 col-lg-3"> <ul className="list-group"> <h5 className="mb-2">Ingredients</h5> {ingredientList} </ul> </div> <div className="col-sm-12 col-lg-7"> <h5 className="mb-2">Preparation Instructions</h5> <div dangerouslySetInnerHTML={{ __html: `${recipeInstruction}` }} /> </div> <div className="col-sm-12 col-lg-2"> <button type="button" className="btn btn-danger" onClick={this.deleteRecipe}> Delete Recipe </button> </div> </div> <Link to="/recipes" className="btn btn-link"> Back to recipes </Link> </div> </div> ); ...
Tại thời điểm này trong hướng dẫn, file Recipe.jsx
hoàn chỉnh của bạn sẽ trông giống như sau:
import React from "react"; import { Link } from "react-router-dom"; class Recipe extends React.Component { constructor(props) { super(props); this.state = { recipe: { ingredients: "" } }; this.addHtmlEntities = this.addHtmlEntities.bind(this); this.deleteRecipe = this.deleteRecipe.bind(this); } addHtmlEntities(str) { return String(str) .replace(/</g, "<") .replace(/>/g, ">"); } componentDidMount() { const { match: { params: { id } } } = this.props; const url = `/api/v1/show/${id}`; fetch(url) .then(response => { if (response.ok) { return response.json(); } throw new Error("Network response was not ok."); }) .then(response => this.setState({ recipe: response })) .catch(() => this.props.history.push("/recipes")); } deleteRecipe() { const { match: { params: { id } } } = this.props; const url = `/api/v1/destroy/${id}`; const token = document.querySelector('meta[name="csrf-token"]').content; fetch(url, { method: "DELETE", headers: { "X-CSRF-Token": token, "Content-Type": "application/json" } }) .then(response => { if (response.ok) { return response.json(); } throw new Error("Network response was not ok."); }) .then(() => this.props.history.push("/recipes")) .catch(error => console.log(error.message)); } render() { const { recipe } = this.state; let ingredientList = "No ingredients available"; if (recipe.ingredients.length > 0) { ingredientList = recipe.ingredients .split(",") .map((ingredient, index) => ( <li key={index} className="list-group-item"> {ingredient} </li> )); } const recipeInstruction = this.addHtmlEntities(recipe.instruction); return ( <div className=""> <div className="hero position-relative d-flex align-items-center justify-content-center"> <img src={recipe.image} alt={`${recipe.name} image`} className="img-fluid position-absolute" /> <div className="overlay bg-dark position-absolute" /> <h1 className="display-4 position-relative text-white"> {recipe.name} </h1> </div> <div className="container py-5"> <div className="row"> <div className="col-sm-12 col-lg-3"> <ul className="list-group"> <h5 className="mb-2">Ingredients</h5> {ingredientList} </ul> </div> <div className="col-sm-12 col-lg-7"> <h5 className="mb-2">Preparation Instructions</h5> <div dangerouslySetInnerHTML={{ __html: `${recipeInstruction}` }} /> </div> <div className="col-sm-12 col-lg-2"> <button type="button" className="btn btn-danger" onClick={this.deleteRecipe}> Delete Recipe </button> </div> </div> <Link to="/recipes" className="btn btn-link"> Back to recipes </Link> </div> </div> ); } } export default Recipe;
Lưu và thoát khỏi file .
Khởi động lại server ứng dụng và chuyển đến trang chủ. Nhấp vào nút Xem Công thức nấu ăn để xem tất cả các công thức nấu ăn hiện có, xem bất kỳ công thức riêng lẻ nào và nhấp vào nút Xóa Công thức nấu ăn trên trang để xóa bài viết. Bạn sẽ được chuyển hướng đến trang công thức nấu ăn và công thức đã xóa sẽ không còn nữa.
Với nút xóa đang hoạt động, bây giờ bạn có một ứng dụng công thức nấu ăn đầy đủ chức năng!
Kết luận
Trong hướng dẫn này, bạn đã tạo một ứng dụng công thức nấu ăn với Ruby on Rails và giao diện user React, sử dụng PostgreSQL làm database và Bootstrap để tạo kiểu. Nếu bạn muốn xem qua nhiều nội dung Ruby on Rails hơn, hãy xem phần Bảo mật Truyền thông của ta trong hướng dẫn Ứng dụng Rails ba tầng Sử dụng SSH Tunnels hoặc xem loạt bài Cách viết mã trong Ruby của ta để làm mới các kỹ năng Ruby của bạn. Để tìm hiểu sâu hơn về React, hãy thử bài viết Cách hiển thị dữ liệu từ API DigitalOcean với React của ta .
Các tin liên quan