如何将React应用与Rails API整合在一起
Ruby on Rails是一个用于开发服务器端应用程序的流行网络框架。世界各地的大多数应用程序都是用Ruby on Rails构建的。开发人员发现它很有用,因为它提供了开发和维护现代网络应用的各种工具。它得到了经验丰富的程序员和一个活跃的在线社区的支持,并不断改进。
另一方面,React是一个前端的JavaScript库,用于构建客户端的用户界面。由于React支持状态管理、组件架构和虚拟文档对象模型(DOM),因此前端开发变得简单、有序和高效。
当前端开发与服务器端编码分离时,适合当前趋势的强大应用程序得到了保证。
在这篇文章中,我们将利用React和Ruby on Rails来创建一个简单的待办事项列表应用程序。我们将使用React来创建应用程序的前端,从后端检索JSON格式的数据,后端将在Rails上运行。
前提条件
- 安装[Ruby on Rails]框架
- 安装了[Node.js]和[npm]包
- 安装了[Yarn]包管理器
- 安装了[Heroku CLI]
- 安装了一个文本编辑器,最好是[VS代码]
- 安装了网络浏览器,最好是[谷歌浏览器]
- 精通[React]、[Ruby on Rails]、[JavaScript]、[Heroku]和[Axios]
设置后端 - Rails API
首先,我们将创建后端,并浏览到通过在终端运行以下命令创建的项目目录。
$ rails new tdlist-api --api
$ cd tdlist-api
我们将继续在VS代码编辑器中打开项目目录,并通过添加以下几行来修改文件Gemfile 。
gem 'rack-cors', :require => 'rack/cors'
注意,我们以后将使用Heroku 来部署我们的应用程序。我们将需要在Gemfile 开发组部分添加sqllite3 ,用于开发和测试,如下所示。
group :development, :test do
# for development purposes
gem 'sqlite3'
end
如果需要,我们可以继续将pg gem添加到生产组部分,用于生产目的,以便利用Postgresql ,如下图所示。
group :production do
# for production purposes
gem 'pg'
end
我们将需要为我们的应用程序的后台生成我们的模型和控制器。我们将首先通过运行下面的命令生成名为Tdlist 的模型。
$ rails g model Tdlist title:string done:boolean
然后,我们通过执行下面的命令来生成名为Tdlists 的控制器。
$ rails g controller Tdlists index create update destroy
最后,下面的命令将在SQLite 数据库实例中生成一个包含数据的表,名为tdlists 。
$ rails db:migrate
我们的后端现在已经设置好了。
接下来,在config/routes.rb 文件中,为我们的后端API指定新的路由,如下所示。
Rails.application.routes.draw do
scope '/api/version1' do
resources :tdlists
end
end
我们已经使用resources ,在我们的后端利用POST,PUT,GET, 和DELETE 行动。
到现在为止,我们正在处理控制器。接下来,我们将在下面的代码中利用我们上面定义的动作。
导航到app/controller/tdslist_controller.rb 文件并粘贴以下代码。
class TdslistController < ApplicationController
def index
tdlists = Tdlist.order("created_at DESC")
render json: tdlists
end
def create
tdlist = Tdlist.create(tdlist_param)
render json: tdlist
end
def update
tdlist = Tdlist.find(params[:id])
tdlist.update(tdlist_param)
render json: tdlist
end
def destroy
tdlist = Tdlist.find(params[:id])
tdlist.destroy
head :no_content, status: :ok
end
private
def tdlist_param
params.require(:tdlist).permit(:title, :done)
end
end
接下来,我们将通过在终端运行下面的命令来启动我们的服务器。
$ rails server
然后,我们导航到链接http://localhost:3000/api/version1/tdlists。由于到目前为止还没有数据,所以将显示一个空白页面。
在db/seeds.rb 文件中,填入一些待办事项,并检查我们的API是否如预期那样工作。
Tdlist.create(title: "Schedule meetings: IT, Accounts, HR", done: false)
Tdlist.create(title: "Visit children's home: perform duties", done: false)
然后我们执行下面的命令。
$ rails db:seed
$ rails server
我们可以继续刷新我们的页面,以下JSON数据将显示在浏览器上。

我们的后端现在已经完成了。我们将配置我们的前端运行在port 4000 ,后端运行在port 3000 。然后我们将使用Heroku CLI ,在本地开发环境中运行我们的应用程序。
为了实现这一目标,我们将在项目根目录下创建一个名为Procfile.windows 的文件,并粘贴下面几行命令。
api: bundle exec rails server –p 3000
web: yarn --cwd tdlist-app start
上面的命令同时执行React应用程序和Heroku上的Rails服务器。
创建前端--React应用程序
我们将通过运行下面的命令开始全局地创建React应用程序。
$ npm install -g create-react-app
$ create-react-app tdlist-app
在Rails根目录下,将生成一个名为tdlist-app ,包含React文件和组件的新文件夹。然后我们将执行下面的命令来启动React应用程序。
$ yarn --cwd tdlist-app start
运行上面的命令后,将显示默认的React页面。

接下来,我们将指定React应用程序在开发模式下,我们的Rails服务器运行在哪个端口上。为了实现这一点,我们将编辑文件tdlist-app/package.json ,添加下面这行命令。
"proxy": "http://localhost:3000"
上面这一行指示React应用程序在开发模式下通过代理与使用3000端口的后端进行通信。为了从应用程序中调用运行在链接http://localhost:3000/api/version1/tdlists上的后端API,我们只需要调用/api/version1/tdlists ,而不是整个链接。
我们还需要用以下几行更新我们的package.json ,以确保我们的React应用程序运行在port 4000 ,而不是默认的port 3000 。
"start": "set PORT=4000 && react-scripts start"
我们更新后的package.json ,将出现如下图所示。

现在我们将通过运行下面的命令来调用我们之前创建的Procfile.windows 文件。
$ heroku local -f Procfile.windows
上面的命令运行Rail API和React应用程序。要访问它,我们可以浏览到http://localhost:4000。
创建React组件
在这一部分,我们将建立我们的应用程序将使用的组件。浏览到tdlist-app/src/components 目录,创建一个名为TdlistsContainer.js 的新文件,并粘贴以下代码。
import React, { Component } from "react";
class TdlistsContainer extends Component {
render() {
return (
<div>
<div className="taskContainer">
<input
className="newTask"
type="text"
placeholder="Input a New Task and Press Enter"
maxLength="75"
/>
</div>
<div className="wrapItems">
<ul className="listItems"></ul>
</div>
</div>
);
}
}
export default TdlistsContainer;
然后在App.js 中导入新的组件。
import React, { Component } from "react";
import "./App.css";
import TdlistsContainer from "./components/TdlistsContainer";
class App extends Component {
render() {
return (
<div className="mainContainer">
<div className="topHeading">
<h1>A Simple To-Do List App</h1>
</div>
<TdlistsContainer />
</div>
);
}
}
export default App;
然后编辑App.css 。
.mainContainer {
width: 35%;
height: 500px;
position: relative;
border-radius: 7px;
margin: 0 auto;
}
.wrapItems {
position: absolute;
bottom: 55px;
top: 170px;
right: 0;
left: 0;
overflow: auto;
}
.topHeading {
color: rgb(48, 2, 2);
font-family: "Times New Roman", Times, serif, sans-serif;
padding: 7px;
text-align: center;
}
ul.listItems {
padding: 0 30px;
}
input.itemCheckbox {
margin-right: 8px;
position: relative;
-webkit-appearance: none;
float: left;
border: 1.5px solid #ccc;
width: 18px;
height: 18px;
border-radius: 7px;
cursor: pointer;
text-align: center;
outline: none;
margin-left: 7px;
font-weight: bold;
}
li.item {
font-family: "Times New Roman", Times, serif;
list-style-type: none;
font-size: 1.5em;
border-bottom: 2px solid rgba(80, 2, 90, 0.411);
padding: 5px 0;
}
li.item:hover .removeItemButton {
opacity: 2;
visibility: visible;
}
input.itemCheckbox:checked:after {
color: green;
width: 15px;
content: "\2713";
font-size: 15px;
display: block;
height: 15px;
left: 0.7px;
position: absolute;
bottom: 1.8px;
}
input.itemCheckbox:checked + label.itemDisplay {
color: #f807a8;
text-decoration: line-through;
}
input.itemCheckbox + label.itemDisplay {
color: black;
}
input[placeholder] {
font-size: 1.2em;
}
.taskContainer {
padding: 15px;
}
.removeItemButton {
float: right;
visibility: hidden;
color: red;
background: rgba(0, 0, 0, 0);
font-size: 25px;
font-weight: bold;
line-height: 0;
border: 1px solid white;
border-radius: 50%;
padding: 10px 5px;
opacity: 0;
margin-right: 7px;
cursor: pointer;
}
.taskContainer .newTask {
padding: 10px;
width: 100%;
box-sizing: border-box;
border-radius: 25px;
}
接下来,我们将刷新前台链接:http://localhost:4000,并显示以下页面。

调用API
在这一部分,我们将从我们的后台获取数据。我们将使用Axios 来获取或存储数据。首先,我们将通过运行以下命令来安装Axios ,然后将其导入我们的组件中。
$ cd tdlist-app
$ npm install axios --save
获取待办事项清单项目
我们将编辑我们的组件来初始化状态和组件的行为。然后我们将添加componentDidMount 函数来加载待办事项列表,如图所示。
import React, { Component } from "react";
import axios from "axios";
class TdlistsContainer extends Component {
constructor(props) {
super(props);
this.state = {
tdlists: [],
};
}
loadTdlists() {
axios
.get("/api/version1/tdlists")
.then((res) => {
this.setState({ tdlists: res.data });
})
.catch((error) => console.log(error));
}
componentDidMount() {
this.loadTdlists();
}
render() {
return (
<div>
<div className="taskContainer">
<input
className="newTask"
type="text"
placeholder="Input a New Task and Press Enter"
maxLength="75"
onKeyPress={this.createTodo}
/>
</div>
<div className="wrapItems">
<ul className="listItems">
{this.state.tdlists.map((tdlist) => {
return (
<li className="item" tdlist={tdlist} key={tdlist.id}>
<input className="itemCheckbox" type="checkbox" />
<label className="itemDisplay">{tdlist.title}</label>
<span className="removeItemButton">x</span>
</li>
);
})}
</ul>
</div>
</div>
);
}
}
export default TdlistsContainer;
现在,我们需要重新启动我们的Heroku local ,并刷新页面,下面的结果将被显示出来。

添加一个新的待办事项列表项目
为了添加一个新的待办事项列表项,我们将调用POST/api/version1/tdlists 。我们需要一个新的函数,使我们能够添加新的待办事项,然后我们继续更新状态。我们将利用immutability-helper来更新状态。我们将运行下面的命令来安装这个包,然后我们把它导入我们的组件中。
$ npm install immutability-helper --save
import update from "immutability-helper";
然后,我们将更新文本框的属性,使其有一个onKeyPress 事件,如下所示。
<input
className="newTask"
type="text"
placeholder="Input a New Task and Press Enter"
maxLength="75"
onKeyPress={this.newTdlist}
/>
然后,我们创建一个newTdlist 函数,如下所示。
newTdlist = (e) => {
if (e.key === "Enter" && !(e.target.value === "")) {
axios
.post("/api/version1/tdlists", { tdlist: { title: e.target.value } })
.then((res) => {
const tdlists = update(this.state.tdlists, {
$splice: [[0, 0, res.data]],
});
this.setState({
tdlists: tdlists,
inputValue: "",
});
})
.catch((error) => console.log(error));
}
};
在上面的代码片段中,我们通过使用$splice 函数,在tdlists 数组的顶部添加了一个新的待办事项。然后我们可以继续添加一个新的待办事项来测试该应用程序。
我们可以注意到,在添加一个新的待办事项后,文本框的值保持不变。为了清除文本框,我们添加以下代码。
this.state = {
tdlists: [],
inputValue: "",
};
为了用我们在上面的代码片断中定义的新参数更新状态,我们粘贴下面的代码。
this.setState({
tdlists: tdlists,
inputValue: "",
});
我们将继续添加一个新的函数,每当文本框的值发生变化时就调用这个函数。
handleChange = (e) => {
this.setState({ inputValue: e.target.value });
};
最后,我们将编辑文本框,使其具有新的属性。
<input
className="newTask"
type="text"
placeholder="Input a New Task and Press Enter"
maxLength="75"
onKeyPress={this.newTdlist}
value={this.state.inputValue}
onChange={this.handleChange}
/>
更新待办事项列表项目
为了将待办事项列表标记为已完成,我们将修改复选框元素,并创建一个新的函数来更新状态,如下所示。
<input
className="itemCheckbox"
type="checkbox"
checked={tdlist.done}
onChange={(e) => this.modifyTdlist(e, tdlist.id)}
/>
modifyTdlist = (e, id) => {
axios
.put(`/api/version1/tdlists/${id}`, { tdlist: { done: e.target.checked } })
.then((res) => {
const tdlistIndex = this.state.tdlists.findIndex(
(x) => x.id === res.data.id
);
const tdlists = update(this.state.tdlists, {
[tdlistIndex]: { $set: res.data },
});
this.setState({
tdlists: tdlists,
});
})
.catch((error) => console.log(error));
};
删除待办事项清单项目
要删除待办事项,我们将更新span 元素,如下图所示。
<span
className="removeItemButton"
onClick={(e) => this.removeTdlist(tdlist.id)}
>
x
</span>
然后我们将创建一个removeTdlist 函数。
removeTdlist = (id) => {
axios
.delete(`/api/version1/tdlists/${id}`)
.then((res) => {
const tdlistIndex = this.state.tdlists.findIndex((x) => x.id === id);
const tdlists = update(this.state.tdlists, {
$splice: [[tdlistIndex, 1]],
});
this.setState({
tdlists: tdlists,
});
})
.catch((error) => console.log(error));
};
该应用程序现在已经准备好进行测试了。
收尾工作
我们已经成功地实现了一个React应用程序并创建了组件,TdlistsContainer 。我们已经使用React状态处理了我们的应用程序的行为。
另一方面,通过Rails,我们建立了一个后端,处理JSON数据,正如我们处理我们的前端一样。我们使用主容器来渲染用户界面,而不是使用组件来使事情变得简单。这让我们有更多的时间来关注其他重要的概念和数据流。