欢迎来到您的下一个 MERN 项目,在这里您将使用 MERN (MongoDB,Express,React,Node.js)框架构建一个非常棒的短视频应用。后端在 Heroku 托管,前端站点使用 Firebase 托管。Material-UI ( https://material-ui.com )提供项目中的图标。
这个 web 应用显示存储在 MongoDB 中的短视频,点击它就可以播放。您可以通过再次点按它来暂停它。这款网络应用还具有非常平滑的垂直滚动功能,可以显示更多视频。在图 3-1 中,可以看到 app 最终部署的版本。
图 3-1
部署版本
首先使用 React,然后移动到后端。打开您的终端并创建一个short-video-mern文件夹。在里面,使用create-react-app创建一个名为短视频前端的新应用。以下是命令。
mkdir short-video-mern
cd short-video-mern
npx create-react-app short-video-frontend由于前端站点是通过 Firebase 托管的,所以可以在 create-react-app 创建 React app 的同时创建基本设置。按照第 1 章中的设置说明,我在 Firebase 控制台中创建了短视频 mern。
回到 React 项目,将cd转到short-video-frontend目录。用npm start启动 React 应用。
cd short-video-frontend
npm start在index.js、App.js和App.css中删除文件和基本设置就像在第 2 章中所做的一样。遵循这些指示。
图 3-2 显示了该应用在 localhost 上的外观。
图 3-2
初始应用
接下来,在src文件夹中创建一个components文件夹。在components文件夹中创建两个文件Video.js和Video.css。在Video.js文件中,添加一个video标签和一个垂直视频链接。我在我的频道上使用了我的 YouTube 短视频的链接。
以下是Video.js内容。
import React from 'react'
import './Video.css'
const Video = () => {
return (
<div className="video">
<video
src="https://res.cloudinary.com/dxkxvfo2o/video/upload/v1608169738/video1_cvrjfm.mp4"
className="video__player"
loop
>
</video>
</div>
)
}
export default Video在本地主机上的App.js文件中包含Video组件。更新后的代码用粗体标记。
import './App.css';
import Video from './components/Video';
function App() {
return (
<div className="app">
<div className="app__videos">
<Video />
<Video />
</div>
</div>
);
}
export default App;接下来,将基本样式放在App.css文件中,包括用于scroll-snap-type的样式,它们是用于滚动的。你还需要让一切居中。接下来,为app__videos类添加一些样式并隐藏滚动条。
html{
scroll-snap-type: y mandatory;
}
.app{
height: 100vh;
background-color: black;
display: grid;
place-items: center;
}
.app__videos{
position:relative;
height: 800px;
border-radius: 20px;
overflow: scroll;
width: 80%;
max-width: 500px;
scroll-snap-type: y mandatory;
}
.app__videos::-webkit-scrollbar{
display: none;
}
.app__videos{
-ms-overflow-style: none;
scrollbar-width: none;
}图 3-3 显示了该应用在 localhost 上的外观。
图 3-3
显示的视频
您还需要设计Video.css文件中的video和video__player类的样式。您在这里再次使用了scroll-snap-type。
.video{
position: relative;
background-color: white;
width: 100%;
height:100%;
scroll-snap-align: start;
}
.video__player{
object-fit: fill;
width: 100%;
height: 100%;
}捕捉特征完成。当你滚动时,它平稳地把你带到下一个视频,如图 3-4 所示。此外,通过 CSS,边缘在所有方面都变得完美。
图 3-4
捕捉特征
目前,视频无法播放。要让它们播放,必须使用一个引用(或 ref)。React 在虚拟 DOM 上工作。一般情况下,只需要在特殊情况下访问 DOM(文档对象模型),使用 refs 访问 DOM 元素。在这种情况下,您需要访问<video> HTML 元素,以便能够访问play()和pause()属性,这些属性只能通过引用获得。
首先,导入useRef和useState钩子以获得videoRef变量,该变量在 video 元素中使用,在这里创建一个onClick处理程序来触发一个handleVideoPress函数。
在handleVideoPress函数内部,用playing状态变量检查视频是否播放,然后用videoRef.current.pause()设置暂停,将播放状态改为 false。你在else区块做相反的动作。
更新后的Video.js内容以粗体标记。
import React , { useRef, useState } from 'react'
import './Video.css'
const Video = () => {
const [playing, setPlaying] = useState(false)
const videoRef = useRef(null)
const handleVideoPress = () => {
if(playing){
videoRef.current.pause()
setPlaying(false)
} else {
videoRef.current.play()
setPlaying(true)
}
}
return (
<div className="video">
<video
src="https://res.cloudinary.com/dxkxvfo2o/video/upload/v1608169738/video1_cvrjfm.mp4"
className="video__player"
loop
ref={videoRef}
onClick={handleVideoPress}
>
</video>
</div>
)
}
export default Video点击视频在本地主机上播放。再次点按它以暂停。
让我们处理第二个组件**,**,它显示了用户名、视频标题和视频页脚中的滚动滚动条。
在components文件夹中创建两个文件VideoFooter.js和VideoFooter.css。然后将VideoFooter组件包含在Video.js文件中。更新后的代码用粗体标记。
import React , { useRef, useState } from 'react'
import './Video.css'
import VideoFooter from './VideoFooter'
const Video = () => {
...
return (
<div className="video">
<video
src="https://res.cloudinary.com/dxkxvfo2o/video/upload/v1608169738/video1_cvrjfm.mp4"
className="video__player"
loop
ref={videoRef}
onClick={handleVideoPress}
>
</video>
<VideoFooter />
</div>
)
}
export default Video接下来,在VideoFooter.js文件中添加一个包含用户名的h3标签和一个包含描述的p标签。
import React from 'react'
import './VideoFooter.css'
const VideoFooter = () => {
return (
<div className="videoFooter">
<div className="videoFooter__text">
<h3>@nabendu82</h3>
<p>Macbook Air to new Windows editing beast</p>
</div>
</div>
)
}
export default VideoFooter接下来,在VideoFooter.css文件中设置它们的样式。
.videoFooter{
position: relative;
color: white;
bottom: 150px;
margin-left: 40px;
display: flex;
}
.videoFooter__text{
flex: 1;
}
.videoFooter__text > h3{
padding-bottom: 20px;
}
.videoFooter__text > p{
padding-bottom: 20px;
}图 3-5 显示了本地主机上的文本。
图 3-5
初始页脚
让我们首先安装 Material-UI,它提供了图标。根据 Material-UI 文档进行两次 npm 安装。通过short-video-frontend文件夹中的集成端子安装铁芯。
npm i @material-ui/core @material-ui/icons是时候在VideoFooter.js文件中使用了。在videoFooter__ticker div 中包含音符图标MusicNoteIcon,它是从 Material-UI 导入的。
更新的内容用粗体标记。
import React from 'react'
import './VideoFooter.css'
import MusicNoteIcon from '@material-ui/icons/MusicNote'
const VideoFooter = () => {
return (
<div className="videoFooter">
<div className="videoFooter__text">
<h3>@nabendu82</h3>
<p>Macbook Air to new Windows editing beast</p>
<div className="videoFooter__ticker">
<MusicNoteIcon className="videoFooter__icon" />
</div>
</div>
</div>
)
}
export default VideoFooter这个项目的特色是一个漂亮的跑马灯。为此,您在short-video-frontend文件夹中安装一个名为react-ticker的包。
npm i react-ticker接下来,在VideoFooter.js文件中包含文档中的股票代码和唱片(或旋转光盘)图像。正如你在新闻频道底部看到的,滚动条在屏幕上移动文本。还显示了一个录制/旋转的光盘图像,您可以很快在其中添加漂亮的动画。
更新的内容用粗体标记。
import React from 'react'
import './VideoFooter.css'
import MusicNoteIcon from '@material-ui/icons/MusicNote'
import Ticker from 'react-ticker'
const VideoFooter = () => {
return (
<div className="videoFooter">
<div className="videoFooter__text">
<h3>@nabendu82</h3>
<p>Macbook Air to new Windows editing beast</p>
<div className="videoFooter__ticker">
<MusicNoteIcon className="videoFooter__icon" />
<Ticker mode="smooth">
{({ index }) => (
<>
<p>I am a Windows PC</p>
</>
)}
</Ticker>
</div>
</div>
<img className="videoFooter__record" src="https://static.thenounproject.com/png/934821-200.png" alt="video footer" />
</div>
)
}
export default VideoFooter接下来,在VideoFooter.css文件中为滚动条和录制的图像添加样式。在这里,您将滚动条与音乐图标对齐,并添加动画来移动录制的图像。
将以下内容添加到VideoFooter.css文件中。
.videoFooter__icon{
position: absolute;
}
.videoFooter__ticker > .ticker{
height: fit-content;
margin-left: 30px;
width: 60%;
}
.videoFooter__record{
animation: spinTheRecord infinite 5s linear;
height: 50px;
filter: invert(1);
position: absolute;
bottom: 0;
right: 20px;
}
@keyframes spinTheRecord {
from {
transform: rotate(0deg)
}
to {
transform: rotate(360deg)
}
}图 3-6 显示了 localhost 上的页脚组件,包括一个滚动滚动条和旋转圆盘。
图 3-6
页脚完成
现在让我们创建一个侧边栏组件,它在视频的右侧显示图标。
在components文件夹中创建两个文件VideoSidebar.js和VideoSidebar.css。您还需要包含Video.js文件。更新后的代码用粗体标记。
import React , { useRef, useState } from 'react'
import './Video.css'
import VideoFooter from './VideoFooter'
import VideoSidebar from './VideoSidebar'
const Video = () => {
...
return (
<div className="video">
<video
src="https://res.cloudinary.com/dxkxvfo2o/video/upload/v1608169738/video1_cvrjfm.mp4"
className="video__player"
loop
ref={videoRef}
onClick={handleVideoPress}
>
</video>
<VideoFooter />
<VideoSidebar />
</div>
)
}
export default Video接下来,更新VideoSidebar.js文件。这里,你使用了不同的材质界面图标。您还可以使用一个状态变量来保存 like 图标是否被按下;如果是这样,它会从空心图标变为实心图标,并且计数也会改变。
import React, { useState } from 'react'
import './VideoSidebar.css'
import FavoriteIcon from '@material-ui/icons/Favorite'
import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder'
import MessageIcon from '@material-ui/icons/Message'
import ShareIcon from '@material-ui/icons/Share'
const VideoSidebar = () => {
const [liked, setLiked] = useState(false)
return (
<div className="videoSidebar">
<div className="videoSidebar__button">
{ liked ? <FavoriteIcon fontSize="large" onClick={e => setLiked(false)} /> : <FavoriteBorderIcon fontSize="large" onClick={e => setLiked(true)} /> }
<p>{liked ? 101 : 100}</p>
</div>
<div className="videoSidebar__button">
<MessageIcon fontSize="large" />
<p>345</p>
</div>
<div className="videoSidebar__button">
<ShareIcon fontSize="large" />
<p>109</p>
</div>
</div>
)
}
export default VideoSidebar接下来,更新VideoSidebar.css文件。
.videoSidebar{
position: absolute;
top: 50%;
right: 10px;
color: white;
}
.videoSidebar__button{
padding: 20px;
text-align: center;
}图 3-7 展示了这些可爱的图标,视频侧边栏就做好了。
图 3-7
侧栏已完成
来自App.js文件的所有数据被传递给子组件。您使组件成为动态的,以便可以向它们传递道具。像在 React 中一样,使用 props 将数据从父组件传递到子组件。视频侧边栏是第一个要处理的组件。在VideoSidebar.js中,传递数字作为道具。
更新的内容用粗体标记。
...
const VideoSidebar = ({ likes, shares, messages }) => {
const [liked, setLiked] = useState(false)
return (
<div className="videoSidebar">
<div className="videoSidebar__button">
{ liked ? <FavoriteIcon fontSize="large" onClick={e => setLiked(false)} /> : <FavoriteBorderIcon fontSize="large" onClick={e => setLiked(true)} /> }
<p>{liked ? likes + 1 : likes }</p>
</div>
<div className="videoSidebar__button">
<MessageIcon fontSize="large" />
<p>{messages}</p>
</div>
<div className="videoSidebar__button">
<ShareIcon fontSize="large" />
<p>{shares}</p>
</div>
</div>
)
}
export default VideoSidebar同样,在VideoFooter.js文件中传递字符串作为道具。
更新的内容用粗体标记。
...
const VideoFooter = ({ channel, description, song }) => {
return (
<div className="videoFooter">
<div className="videoFooter__text">
<h3>@{channel} </h3>
<p>{description}</p>
<div className="videoFooter__ticker">
<MusicNoteIcon className="videoFooter__icon" />
<Ticker mode="smooth">
{({ index }) => (
<>
<p>{song}</p>
</>
)}
</Ticker>
</div>
</div>
<img className="videoFooter__record" src="https://static.thenounproject.com/png/934821-200.png" alt="video footer" />
</div>
)
}
export default VideoFooter您希望进一步从应用组件钻取道具,以获得不同的视频文件。让我们将这些道具添加到Video.js文件中并使用它们。
更新的内容用粗体标记。
...
const Video = ({ url, channel, description, song, likes, shares, messages }) => {
...
return (
<div className="video">
<video
src={url}
className="video__player"
loop
ref={videoRef}
onClick={handleVideoPress}
>
</video>
<VideoFooter channel={channel} description={description} song={song} />
<VideoSidebar likes={likes} shares={shares} messages={messages} />
</div>
)
}
export default Video在App.js中,你通过所有的道具,可以通过两个不同的视频。
更新的内容用粗体标记。
...
function App() {
return (
<div className="app">
<div className="app__videos">
<Video
url="https://res.cloudinary.com/dxkxvfo2o/video/upload/v1608169738/video1_cvrjfm.mp4"
channel="nabendu82"
description="Macbook Air to new Windows editing beast"
song="I am a Windows PC"
likes={345}
shares={200}
messages={90}
/>
<Video
url="https://res.cloudinary.com/dxkxvfo2o/video/upload/v1608169739/video2_mecbdo.mp4"
channel="thewebdev"
description="Tuesday morning editing on kdenlive in Windows"
song="Kdenlive is great"
likes={445}
shares={290}
messages={109}
/>
</div>
</div>
);
}
export default App;前端完成了,该开始后端了。
让我们转到后端,从 Node.js 代码开始。打开一个新的终端窗口,在根目录下创建一个新的short-video-backend文件夹。移动到short-video-backend目录后,输入git init命令,这是 Heroku 稍后需要的。
mkdir short-video-backend
cd short-video-backend
git init接下来,通过在终端中输入npm init命令来创建package.json文件。你被问了一堆问题;对于大多数情况,只需按下回车键。你可以提供描述和作者,但不是强制的。你一般在server.js做进入点,这是标准的(见图 3-8 )。
图 3-8
初始服务器设置
一旦package.json被创建,你需要创建包含node_modules的.gitignore文件,因为你不想以后将node_modules推送到 Heroku。以下是.gitignore文件的内容。
node_modules接下来,打开package.json.行"type" : "module"需要在 Node.js 中启用类似 React 的导入,包括一个启动脚本来运行server.js文件。
更新的内容用粗体标记。
{
"name": "short-video-backend",
"version": "1.0.0",
"description": " The short video 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"
}在开始之前,您需要安装两个软件包。打开终端,在short-video-backend文件夹中安装 Express 和 Mongoose。正如第 2 章所讨论的,Express 是 Node.js 框架,通过它你可以轻松构建后端代码。Mongoose 是绑定 Node.js 和 MongoDB 所需的库,因此它是负责在 Node.js 代码中创建模式的桥梁。
npm i express mongooseMongoDB 的设置与第 1 章中描述的相同。按照这些说明,创建一个名为的新项目。
在继续之前,将nodemon安装在short-video-backend文件夹中。它帮助server.js中的变化瞬间重启 Node 服务器。
npm i nodemon接下来,在short-video-backend文件夹中创建一个server.js文件。在这里,您导入 Express 和 Mongoose 包。然后使用 Express 创建一个运行在端口 9000 上的port变量。
第一个 API 端点是一个由app.get()创建的简单 GET 请求,如果成功,它会显示文本 Hello TheWebDev 。
然后,用app.listen()监听端口。
import express from 'express'
import mongoose from 'mongoose'
//App Config
const app = express()
const port = process.env.PORT || 9000
//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: 9000 控制台日志。为了检查路线是否正常工作,转到http://localhost:9000/查看端点文本,如图 3-9 所示。
图 3-9
本地主机
在 MongoDB 中,您需要创建一个数据库用户并授予网络访问权限。该过程与第 1 章中的解释相同。遵循这些说明,然后获取用户凭证和连接 URL。
在server.js中,创建一个connection_url变量,并将 URL 粘贴到 MongoDB 的字符串中。您需要提供之前保存的密码和数据库名称。
更新后的代码用粗体标记。
...
//App Config
const app = express()
const port = process.env.PORT || 9000
const connection_url = ' mongodb+srv://admin:yourpassword@cluster0.ryj4g.mongodb.net/shortVideoDB?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 中的存储方式。在short-video-backend文件夹中创建一个dbModel.js文件。
这里,shortVideos被认为是一个集合名,您在数据库中存储一个类似于shortVideoSchema的值。它由一个带有 URL、频道、描述、歌曲、喜欢、共享和消息键的对象组成。
import mongoose from 'mongoose'
const shortVideoSchema = mongoose.Schema({
url: String,
channel: String,
description: String,
song: String,
likes: String,
shares: String,
messages: String
})
export default mongoose.model('shortVideos', shortVideoSchema)现在,您可以使用该模式来创建向数据库添加数据的端点。
在server.js中,创建一个到/v2/posts端点的 POST 请求。负载在req.body到 MongoDB。然后使用create()发送dbVideos.如果成功,您将收到状态 201;否则,您会收到状态 500。
接下来,创建/v2/posts的 GET 端点,从数据库中获取数据。你在这里用的是find()。如果成功,您将收到状态 200(否则,状态 500)。
更新后的代码用粗体标记。
import express from 'express'
import mongoose from 'mongoose'
import Videos from './dbModel.js'
...
//API Endpoints
app.get("/", (req, res) => res.status(200).send("Hello TheWebDev"))
app.post('/v2/posts', (req, res) => {
const dbVideos = req.body
Videos.create(dbVideos, (err, data) => {
if(err)
res.status(500).send(err)
else
res.status(201).send(data)
})
})
app.get('/v2/posts', (req, res) => {
Videos.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:9000发送 GET 请求,检查它是否在 Postman 中工作(见图 3-10 )。
图 3-10
获取请求
在处理 POST 请求之前,您需要完成两件事情。首先,实施 CORS。打开终端,在short-video-backend文件夹中安装 CORS。
npm i cors在server.js中,导入 CORS,然后配合app.use()使用。你还需要使用express.json()中间件。
更新后的代码用粗体标记。
import express from 'express'
import mongoose from 'mongoose'
import Cors from 'cors'
import Videos from './dbModel.js'
...
//Middleware
app.use(express.json())
app.use(Cors())
...在 Postman 中,将请求更改为 POST,然后添加http://localhost:9000/v2/posts端点。
接下来,点击身体,选择原始。从下拉菜单中选择 JSON(应用/json) 。在文本编辑器中,从App.js文件中复制数据。通过在关键字中添加双引号来生成数据 JSON。
然后,点击发送按钮。如果一切正确,你得到状态:201 已创建,如图 3-11 所示。
图 3-11
成功消息发布
我类似地插入了其他数据。您需要测试 GET 端点。将请求更改为 GET,然后单击发送按钮。如果一切正确,你得到状态:200 OK ,如图 3-12 所示。
图 3-12
成功消息获取
让我们用axios包把后端钩到前端。打开short-video-frontend文件夹并安装。
npm i axios接下来,在components文件夹中创建一个新的axios.js文件,并创建一个axios的实例。基础 URL 是http://localhost:9000。
import axios from 'axios'
const instance = axios.create({
baseURL: "http://localhost:9000"
})
export default instance在App.js中,导入本地axios。然后使用useEffect钩子对/v2/posts端点进行 API 调用。一旦收到数据,使用setVideos()将其存储在videos状态变量中。
在 return 语句中,去掉硬编码的东西。之后,映射视频数组,并将道具传递给视频组件。
更新的内容用粗体标记。
import React, { useState, useEffect } from 'react';
import './App.css';
import Video from './components/Video';
import axios from './components/axios';
function App() {
const [videos, setVideos] = useState([])
useEffect(() => {
async function fetchData() {
const res = await axios.get("/v2/posts")
setVideos(res.data)
return res
}
fetchData()
}, [])
return (
<div className="app">
<div className="app__videos">
{videos.map(({ url, channel, description, song, likes, shares, messages }) => (
<Video
key={url}
url={url}
channel={channel}
description={description}
song={song}
likes={likes}
shares={shares}
messages={messages}
/>
))}
</div>
</div>
);
}
export default App;可以看到http://localhost:3000/的数据。应用现在已经完成。但是在喜欢的数量上有一个小问题;它显示 3451 而不是 346(见图 3-13 )。
图 3-13。
出现此问题的原因是从数据库中传递字符串数字。在VideoSidebar.js中,在喜欢的前面加一个 + ,把字符串改成数字。
...
<div className="videoSidebar__button">
{ liked ? <FavoriteIcon fontSize="large" onClick={e => setLiked(false)} /> : <FavoriteBorderIcon fontSize="large" onClick={e => setLiked(true)} /> }
<p>{liked ? +likes + 1 : likes}</p>
</div>
'''转到 www.heroku.com 部署后端。按照你在第 1 章中所做的相同步骤,创建一个名为短视频后端的应用。
成功部署后,转到链接。图 3-14 显示了正确的文本。
图 3-14。
在axios.js中,将端点改为 https://short-video-backend.herokuapp.com 。如果一切正常,你的应用应该可以运行了。
import axios from 'axios'
const instance = axios.create({
baseURL: " https://short-video-backend.herokuapp.com"
})
export default instance是时候在 Firebase 中部署前端了。遵循与第 1 章相同的程序。完成此过程后,站点应处于活动状态并正常工作,如图 3-15 所示。
图 3-15。
在本章中,我们创建了一个短视频分享应用。我们在 ReactJS 中构建前端,并在 Firebase 中托管它。后端构建在 NodeJS 中,托管在 Heroku 中。数据库是在 MongoDB 中构建的。














