欢迎来到第 2 章,在这里你将使用 MERN (MongoDB,Express,React,Node.js)框架构建一个约会应用。后端托管在 Heroku,前端站点使用 Firebase 托管。项目中的图标来自 Material-UI。
该 web 应用功能简单,是第一个 MERN 堆栈项目。部署在 Firebase 中的成品 app 的截图如图 2-1 所示。所有数据都来自 MongoDB 数据库,API 端点设置在 Node.js 中。
图 2-1
完成的应用
让我们先回顾一下 React 前端,然后转到后端。打开您的终端并创建一个dating-app-mern文件夹。在里面,使用 create-react-app 创建一个新的 app,名为 dating-app-frontend 。以下是完成此操作的命令。
mkdir dating-app-mern
cd dating-app-mern
npx create-react-app dating-app-frontend由于前端站点是通过 Firebase 托管的,所以让我们在 create-react-app 创建 React app 的同时创建基本设置。按照第 1 章中相同的设置说明,我在 Firebase 控制台中创建了 dating-app-mern。
返回 React 项目,将cd返回到dating-app-frontend目录。用npm start启动 React 应用。
cd dating-app-frontend
npm start接下来,让我们删除一些你不需要的文件。图 2-2 显示了该应用在 localhost 上的外观。
图 2-2
删除文件
让我们删除所有不必要的样板代码。index.js文件应该如下所示。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);App.js只包含文字交友 App MERN 。来自App.css文件的所有内容都已被删除。
import './App.css';
function App() {
return (
<div className="app">
<h1>Dating App MERN </h1>
</div>
);
}
export default App;在index.css中,更新 CSS,使margin: 0位于顶部。
* {
margin: 0;
}图 2-3 显示了该应用在 localhost 上的外观。
图 2-3
初始应用
让我们创建一个标题组件。首先,你必须安装 Material-UI ( https://material-ui.com ),它提供了图标。根据 Material-UI 文档,您需要进行两次 npm 安装。通过dating-app-frontend文件夹中的集成端子安装铁芯。
npm i @material-ui/core @material-ui/icons接下来,在src文件夹中创建一个components文件夹。在components文件夹中创建两个文件——Header.js和Header.css—。Header.js有三样东西:一个人物图标、一个徽标和一个论坛图标。该徽标来自项目的公共目录,默认情况下包含 React 徽标。
以下是Header.js文件的内容。
import React from 'react'
import './Header.css'
import PersonIcon from '@material-ui/icons/Person'
import IconButton from '@material-ui/core/IconButton'
import ForumIcon from '@material-ui/icons/Forum'
const Header = () => {
return (
<div className="header">
<IconButton>
<PersonIcon fontSize="large" className="header__icon" />
</IconButton>
<img className="header__logo" src="logo192.png" alt="header" />
<IconButton>
<ForumIcon fontSize="large" className="header__icon" />
</IconButton>
</div>
)
}
export default Header在本地主机上的App.js文件中包含Header组件。更新后的代码用粗体标记。
import './App.css';
import Header from './components/Header';
function App() {
return (
<div className="app">
<Header />
</div>
);
}
export default App;Header.css文件包含以下内容,包括简单的样式,完成了头。
.header{
display: flex;
align-items: center;
justify-content: space-between;
z-index: 100;
border-bottom: 1px solid #f9f9f9;
}
.header__logo{
object-fit: contain;
height: 40px;
}
.header__icon{
padding: 20px;
}图 2-4 显示了应用现在在 localhost 上的样子。
图 2-4
标题组件
现在让我们来研究第二个部分。在components文件夹中创建两个文件DatingCards.js和DatingCards.css。然后将DatingCards组件包含在App.js文件中。更新后的代码用粗体标记。
import './App.css';
import Header from './components/Header';
import DatingCards from './components/DatingCards';
function App() {
return (
<div className="app">
<Header />
< DatingCards />
</div>
);
}
export default App;在继续之前,您需要安装一个react-tinder-card包。该包具有提供滑动效果的功能。
npm i react-tinder-card接下来,将内容放入DatingCards.js。在这里,在一个people状态变量中,您存储了四个人的姓名和图像。接下来,导入DatingCard,并将其作为组件使用。这里,你使用react-tinder-card文档中提到的道具。
需要swiped和outOfFrame功能。当遍历每个人时,使用imgUrl背景图像并在h3标签中显示姓名。
import React, { useState } from 'react'
import DatingCard from 'react-tinder-card'
import './DatingCards.css'
const DatingCards = () => {
const [people, setPeople] = useState([
{ name: "Random Guy", imgUrl: "https://images.unsplash.com/photo-1520409364224-63400afe26e5?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=658&q=80" },
{ name: "Another Guy", imgUrl: "https://images.unsplash.com/photo-1519085360753-af0119f7cbe7?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=634&q=80" },
{ name: "Random Girl", imgUrl: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=634&q=80" },
{ name: "Another Girl", imgUrl: "https://images.unsplash.com/photo-1529626455594-4ff0802cfb7e?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80" }
])
const swiped = (direction, nameToDelete) => {
console.log("receiving " + nameToDelete)
}
const outOfFrame = (name) => {
console.log(name + " left the screen!!")
}
return (
<div className="datingCards">
<div className="datingCards__container">
{people.map((person) => (
<DatingCard
className="swipe"
key={person.name}
preventSwipe={['up', 'down']}
onSwipe={(dir) => swiped(dir, person.name)}
onCardLeftScreen={() => outOfFrame(person.name)} >
<div style={{ backgroundImage: `url(${person.imgUrl})`}} className="card">
<h3>{person.name}</h3>
</div>
</DatingCard>
))}
</div>
</div>
)
}
export default DatingCardsLocalhost 显示了四个“人”,如图 2-5 所示,但是您需要设计所有的样式。
图 2-5
所有人
在DatingCards.css文件中添加第一个样式,并使datingCards__container成为 flexbox。接下来,将每张卡片设计成包含图片和其他东西的样式。请注意,您正在为每张卡片设置position: relative,这将使元素相对于自身偏移,并提供宽度和高度。
.datingCards__container{
display: flex;
justify-content: center;
margin-top: 10vh;
}
.card{
position: relative;
background-color: white;
width: 600px;
padding: 20px;
max-width: 85vw;
height: 50vh;
box-shadow: 0px 18px 53px 0px rgba(0, 0, 0, 0.3);
border-radius: 20px;
background-size: cover;
background-position: center;
}图 2-6 显示了这在本地主机上的样子。
图 2-6
图像出现
让我们再添加三个样式,从这个 swipe 中可以得到一个 card 类中的类。使用position: absolute创造滑动效果的魔力。在DatingCards.css文件中添加以下内容。
.swipe{
position: absolute;
}
.cardContent{
width: 100%;
height: 100%;
}
.card h3{
position: absolute;
bottom: 0;
margin: 10px;
color: white;
}前端基本完成,如图 2-7 所示。它包含右扫和左扫功能。除了包含滑动按钮的页脚之外,一切都完成了。
图 2-7
几乎完成
现在让我们创建SwipeButtons组件,它是页脚中的按钮。这些按钮增加了应用的风格。因为这是一个简单的应用,所以它们不会起作用。在components文件夹中创建两个文件SwipeButtons.js和SwipeButtons.css。你还需要把它包含在App.js文件中。
更新的内容用粗体标记。
import './App.css';
import Header from './components/Header';
import DatingCards from './components/DatingCards';
import SwipeButtons from './components/SwipeButtons';
function App() {
return (
<div className="app">
<Header />
< DatingCards />
< SwipeButtons />
</div>
);
}
export default App;SwipeButtons.js文件的内容很简单。有五个来自 Material-UI 的图标包裹在IconButton里面。
import React from 'react'
import './SwipeButtons.css'
import ReplayIcon from '@material-ui/icons/Replay'
import CloseIcon from '@material-ui/icons/Close'
import StarRateIcon from '@material-ui/icons/StarRate'
import FavoriteIcon from '@material-ui/icons/Favorite'
import FlashOnIcon from '@material-ui/icons/FlashOn'
import IconButton from '@material-ui/core/IconButton'
const SwipeButtons = () => {
return (
<div className="swipeButtons">
<IconButton className="swipeButtons__repeat">
<ReplayIcon fontSize="large" />
</IconButton>
<IconButton className="swipeButtons__left">
<CloseIcon fontSize="large" />
</IconButton>
<IconButton className="swipeButtons__star">
<StarRateIcon fontSize="large" />
</IconButton>
<IconButton className="swipeButtons__right">
<FavoriteIcon fontSize="large" />
</IconButton>
<IconButton className="swipeButtons__lightning">
<FlashOnIcon fontSize="large" />
</IconButton>
</div>
)
}
export default SwipeButtons接下来,在SwipeButtons.css文件中设置按钮的样式。首先,设计swipeButtons类的样式,并使用position: fixed使其灵活。在一个固定的位置,一个元素保持附着在指定的位置(在这个例子中是底部),甚至当用户滚动时。您还设计了由包创建的MuiIconButton-root类的样式。
在SwipeButtons.css文件中,用不同的颜色设计每个按钮。
.swipeButtons{
position: fixed;
bottom: 10vh;
display: flex;
width: 100%;
justify-content: space-evenly;
}
.swipeButtons .MuiIconButton-root{
background-color: white;
box-shadow: 0px 10px 53px 0px rgba(0, 0, 0, 0.3) !important;
}
.swipeButtons__repeat{
padding: 3vw !important;
color: #f5b748 !important;
}
.swipeButtons__left{
padding: 3vw !important;
color: #ec5e6f !important;
}
.swipeButtons__star{
padding: 3vw !important;
color: #62b4f9 !important;
}
.swipeButtons__right{
padding: 3vw !important;
color: #76e2b3 !important;
}
.swipeButtons__lightning{
padding: 3vw !important;
color: #915dd1 !important;
}图 2-8 显示了本地主机上的项目。
图 2-8
前端完成
让我们从 Node.js 代码开始,转到后端。打开一个新的终端窗口,在根目录下创建一个新的dating-app-backend文件夹。输入git init,因为 Heroku 稍后需要它。
mkdir dating-app-backend
cd dating-app-backend
git init接下来,通过在终端中输入npm init命令来创建一个package.json文件。你被问了几个问题;对于大多数情况,请按回车键。你可以输入一个描述和作者,但不是强制的。您通常可以在server.js设置进入点,因为这是标准(见图 2-9 )。
图 2-9
后端初始设置
一旦package.json被创建,你需要创建包含node_modules的.gitignore文件,因为你不想以后将node_modules推送到 Heroku。以下是.gitignore文件的内容。
node_modules接下来,打开package.json.行"type" : "module"需要在 Node.js 中启用类似 React 的导入,这些模块被称为 ECMA 模块。带有 require 语句的初始模块称为 CommonJS 模块。你可以在 https://blog.logrocket.com/how-to-use-ecmascript-modules-with-node-js/ 了解更多。
您还需要包含一个启动脚本来运行server.js文件。更新的内容用粗体标记。
{
"name": "dating-app-backend",
"version": "1.0.0",
"description": "The dating app backend",
"main": "server.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"author": "Nabendu Biswas",
"license": "ISC"
}在开始之前,您需要安装两个软件包。打开终端,在dating-app-backend文件夹中安装 Express 和 Mongoose。
npm i express mongooseMongoDB 的设置与第 1 章中描述的相同。你需要遵循它并创建一个名为的新项目。
在继续之前,将nodemon安装在dating-app-backend文件夹中。每当您对server.js文件中的代码进行任何更改时,Node 服务器都会立即重启。
npm i nodemon让我们创建初始路由,它通常检查是否一切都设置正确。Node.js 中的 Express 包允许您创建路由,这是大多数互联网的工作方式。大多数后端语言,如 Node.js、Java,都提供了创建这些与数据库交互的路由的功能。初始路由不与数据库交互,只是在您使用 GET 请求访问它时返回一个文本。在dating-app-backend文件夹中创建一个server.js文件。在这里,您首先导入 Express 和 Mongoose 包。接下来,使用 Express 创建一个在端口 8001 上运行的port变量。
第一个 API 端点是一个由app.get()创建的简单 GET 请求,如果成功,它会显示 Hello TheWebDev 文本。
然后你用app.listen()监听 8001 端口。
import express from 'express'
import mongoose from 'mongoose'
//App Config
const app = express()
const port = process.env.PORT || 8001
//Middleware
//DB Config
//API Endpoints
app.get("/", (req, res) => res.status(200).send("Hello TheWebDev"))
//Listener
app.listen(port, () => console.log(`Listening on localhost: ${port}`))在终端中,键入 nodemon server.js 。可以看到监听 localhost: 8001 控制台日志。为了检查路线是否正常工作,转到http://localhost:8001/查看终点文本(见图 2-10 )。
图 2-10
初始路线
在 MongoDB 中,您需要创建一个数据库用户并提供网络访问。该过程与第 1 章中的过程相同。按照这些说明,获取用户凭证和连接 URL。
在server.js中,创建一个connection_url变量,并将 URL 粘贴到从 MongoDB 获得的字符串中。输入您之前保存的密码,并提供一个数据库名称。更新后的代码用粗体标记。
...
//App Config
const app = express()
const port = process.env.PORT || 8001
const connection_url = 'mongodb+srv://admin:yourpassword@cluster0.lggjc.mongodb.net/datingDB?retryWrites=true&w=majority'
//Middleware
//DB Config
mongoose.connect(connection_url, {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true
})
//API Endpoints
app.get("/", (req, res) => res.status(200).send("Hello TheWebDev"))
...MongoDB 以 JSON 格式存储数据,而不是像 Oracle 这样的传统数据库中的常规表结构。您创建了 MongoDB 所需的模式文件。它告诉你如何在 MongoDB 中存储字段。
这里,cards被认为是一个集合名,您在数据库中存储一个类似于cardSchema的值。它由一个有名字的对象和imgUrl键组成。这些是您在 MongoDB 中使用的名称。创建一个dbCards.js文件,将以下内容放入其中。
import mongoose from 'mongoose'
const cardSchema = mongoose.Schema({
name: String,
imgUrl: String
})
export default mongoose.model('cards', cardSchema)现在,您可以使用该模式来创建向数据库添加数据的端点。这里遵循 MVC 模式;这是 web 应用的传统流程。点击 https://medium.com/createdd-notes/understanding-mvc-architecture-with-react-6cd38e91fefd 了解更多信息。
接下来,使用一个 POST 请求,从用户那里获取任何数据,并将其发送到数据库。您可以使用任何端点。例如,如果你写了一篇关于脸书的文章并点击了 POST 按钮,那么一旦发出 POST 请求,你的文章就会被保存在脸书数据库中。
GET 端点从数据库中获取所有数据。同样,你可以给出任何端点。例如,当您浏览脸书的提要时,一个 GET 请求被发送到端点,端点又从脸书数据库获取所有的帖子。
在server.js,中,创建一个到/dating/cards端点的 POST 请求。负载在req.body到 MongoDB。然后你用create()送dbCard。如果成功,您会收到状态 201;否则,您会收到状态 500。更新的内容用粗体标记。
接下来,创建/dating/cards的 GET 端点,从数据库中获取数据。您在这里使用find(),如果成功,将收到状态 200(否则,状态 500)。更新的内容用粗体标记。
import express from 'express'
import mongoose from 'mongoose'
import Cards from './dbCards.js'
...
//API Endpoints
app.get("/", (req, res) => res.status(200).send("Hello TheWebDev"))
app.post('/dating/cards', (req, res) => {
const dbCard = req.body
Cards.create(dbCard, (err, data) => {
if(err) {
res.status(500).send(err)
} else {
res.status(201).send(data)
}
})
})
app.get('/dating/cards', (req, res) => {
Cards.find((err, data) => {
if(err) {
res.status(500).send(err)
} else {
res.status(200).send(data)
}
})
})
//Listener
app.listen(port, () => console.log(`Listening on localhost: ${port}`))要查看路线,让我们使用邮递员应用。下载并安装它。
向http://localhost:8001发送 GET 请求,检查它是否在 Postman 中工作,如图 2-11 所示。
图 2-11
初始路线检查
在处理 POST 请求之前,您需要完成两件事情。第一,实行 First 否则,当您稍后部署应用时,会出现跨来源错误。CORS(跨源资源共享)是限制从一个域访问另一个域的机制。假设你在http://example.com上,想访问 http://mybank.com/accountdetails 。CORS 不会允许你这么做的。只有 http://mybank.com 允许与http://example.com跨原点共享时才允许。
打开终端,在dating-app-backend文件夹中安装 CORS。
npm i cors在server.js中,导入 CORS 并与app.use()一起使用。你还需要使用express.json()中间件。它是必需的,因为您需要它来解析来自 MongoDB 的传入 JSON 对象以读取主体。
更新后的代码用粗体标记。
import express from 'express'
import mongoose from 'mongoose'
import Cors from 'cors'
import Cards from './dbCards.js'
...
//Middleware
app.use(express.json())
app.use(Cors())
...在 Postman 中,将请求更改为 POST,然后添加http://localhost:8001/dating/cards端点。
接下来,点击身体,选择原始。从下拉菜单中选择 JSON(应用/json) 。在文本编辑器中,从DatingCards.js文件中复制数据。通过在关键字中添加双引号来生成数据 JSON。
接下来,点击发送按钮。如果一切正确,您将获得状态:201 已创建(见图 2-12 )。
图 2-12
邮寄路线
您需要测试 GET 端点。将请求更改为 GET,然后单击发送按钮。如果一切正常,你得到状态:200 OK (见图 2-13 )。
图 2-13
获取路线
让我们把后端钩到前端。使用axios包从前端调用。Axios 是一个 JavaScript 库,它向 REST 端点发出 API 请求。您刚刚在后端创建了两个端点。要访问它们,你需要 Axios。打开dating-app-frontend文件夹并安装。
npm i axios接下来,在components文件夹中创建一个新的axios.js文件,然后创建一个axios的实例。基础 URL 是http://localhost:8001。
import axios from 'axios'
const instance = axios.create({
baseURL: "http://localhost:8001"
})
export default instance在DatingCards.js,中,去掉处于people状态的硬编码内容。然后导入本地的axios并使用useEffect钩子对/dating/cards端点进行 API 调用。收到数据后,使用setPeople()功能将其复位。更新后的代码用粗体标记。
import React, { useState, useEffect } from 'react'
import DatingCard from 'react-tinder-card'
import './DatingCards.css'
import axios from './axios'
const DatingCards = () => {
const [people, setPeople] = useState([])
useEffect(() => {
async function fetchData() {
const req = await axios.get("/dating/cards")
setPeople(req.data)
}
fetchData()
}, [])
const swiped = (direction, nameToDelete) => {
console.log("receiving " + nameToDelete)
}
...去http://localhost:3000/看数据。应用现已完成(见图 2-14 )。
图 2-14
应用完成
转到 www.heroku.com 部署后端。你按照第 1 章中的相同步骤创建了一个名为 dating-mern-backend 的应用。
返回axios.js,将端点改为 https://dating-mern-backend.herokuapp.com 。如果一切正常,你的应用应该可以运行了。
import axios from 'axios'
const instance = axios.create({
baseURL: https://dating-mern-backend.herokuapp.com
})
export default instance是时候在 Firebase 中部署前端了。遵循与第 1 章相同的程序。完成此过程后,站点应处于活动状态并正常工作,如图 2-15 所示。
图 2-15
部署的应用
在这一章中,我们在 MERN 堆栈中创建了一个约会应用。我们在 ReactJS 中构建前端,并在 Firebase 中托管它。后端构建在 NodeJS 中,托管在 Heroku 中。数据库是在 MongoDB 中构建的。














