Skip to content

Latest commit

 

History

History
763 lines (535 loc) · 24.7 KB

File metadata and controls

763 lines (535 loc) · 24.7 KB

二、使用 MERN 开发约会应用

欢迎来到第 2 章,在这里你将使用 MERN (MongoDB,Express,React,Node.js)框架构建一个约会应用。后端托管在 Heroku,前端站点使用 Firebase 托管。项目中的图标来自 Material-UI。

该 web 应用功能简单,是第一个 MERN 堆栈项目。部署在 Firebase 中的成品 app 的截图如图 2-1 所示。所有数据都来自 MongoDB 数据库,API 端点设置在 Node.js 中。

img/512020_1_En_2_Chapter/512020_1_En_2_Fig1_HTML.jpg

图 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 托管初始设置

由于前端站点是通过 Firebase 托管的,所以让我们在 create-react-app 创建 React app 的同时创建基本设置。按照第 1 章中相同的设置说明,我在 Firebase 控制台中创建了 dating-app-mern。

React 基本设置

返回 React 项目,将cd返回到dating-app-frontend目录。用npm start启动 React 应用。

cd dating-app-frontend
npm start

接下来,让我们删除一些你不需要的文件。图 2-2 显示了该应用在 localhost 上的外观。

img/512020_1_En_2_Chapter/512020_1_En_2_Fig2_HTML.jpg

图 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 上的外观。

img/512020_1_En_2_Chapter/512020_1_En_2_Fig3_HTML.jpg

图 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.jsHeader.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 上的样子。

img/512020_1_En_2_Chapter/512020_1_En_2_Fig4_HTML.jpg

图 2-4

标题组件

创建约会卡组件

现在让我们来研究第二个部分。在components文件夹中创建两个文件DatingCards.jsDatingCards.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文档中提到的道具。

需要swipedoutOfFrame功能。当遍历每个人时,使用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 DatingCards

Localhost 显示了四个“人”,如图 2-5 所示,但是您需要设计所有的样式。

img/512020_1_En_2_Chapter/512020_1_En_2_Fig5_HTML.jpg

图 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 显示了这在本地主机上的样子。

img/512020_1_En_2_Chapter/512020_1_En_2_Fig6_HTML.jpg

图 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 所示。它包含右扫和左扫功能。除了包含滑动按钮的页脚之外,一切都完成了。

img/512020_1_En_2_Chapter/512020_1_En_2_Fig7_HTML.jpg

图 2-7

几乎完成

创建滑动按钮组件

现在让我们创建SwipeButtons组件,它是页脚中的按钮。这些按钮增加了应用的风格。因为这是一个简单的应用,所以它们不会起作用。在components文件夹中创建两个文件SwipeButtons.jsSwipeButtons.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 显示了本地主机上的项目。

img/512020_1_En_2_Chapter/512020_1_En_2_Fig8_HTML.jpg

图 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 )。

img/512020_1_En_2_Chapter/512020_1_En_2_Fig9_HTML.jpg

图 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 mongoose

MongoDB 设置

MongoDB 的设置与第 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 )。

img/512020_1_En_2_Chapter/512020_1_En_2_Fig10_HTML.jpg

图 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 模式和路由

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 所示。

img/512020_1_En_2_Chapter/512020_1_En_2_Fig11_HTML.jpg

图 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 )。

img/512020_1_En_2_Chapter/512020_1_En_2_Fig12_HTML.jpg

图 2-12

邮寄路线

您需要测试 GET 端点。将请求更改为 GET,然后单击发送按钮。如果一切正常,你得到状态:200 OK (见图 2-13 )。

img/512020_1_En_2_Chapter/512020_1_En_2_Fig13_HTML.jpg

图 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 )。

img/512020_1_En_2_Chapter/512020_1_En_2_Fig14_HTML.jpg

图 2-14

应用完成

将后端部署到 Heroku

转到 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

是时候在 Firebase 中部署前端了。遵循与第 1 章相同的程序。完成此过程后,站点应处于活动状态并正常工作,如图 2-15 所示。

img/512020_1_En_2_Chapter/512020_1_En_2_Fig15_HTML.jpg

图 2-15

部署的应用

摘要

在这一章中,我们在 MERN 堆栈中创建了一个约会应用。我们在 ReactJS 中构建前端,并在 Firebase 中托管它。后端构建在 NodeJS 中,托管在 Heroku 中。数据库是在 MongoDB 中构建的。