使用 MERN 堆栈的绝对力量构建项目案例研究应用程序🔥

2025-05-25

使用 MERN 堆栈的绝对力量构建项目案例研究应用程序🔥

这篇博文重点介绍最重要的活动和理念,帮助您更好地理解和自下而上地构建 MERN 堆栈应用程序。它适合真正有兴趣了解 MERN 堆栈并希望专注于实际需要了解内容的人。
另有一篇单独的文章,您可以详细了解 MERN 堆栈。

https://aviyel.com/post/1323

在本篇博文中,我们将构建一个全栈项目案例研究应用程序,用户可以使用它来跟踪和记录 GitHub 项目,以及使用 GitHub API 和 MERN 堆栈搜索整个 GitHub 项目。本篇博文课程将帮助您学习 MERN 堆栈技术的基础知识以及高级概念和操作。
以下是我们应用程序的最终版本。

演示

演示

设置文件夹结构

在项目目录中创建两个文件夹,分别为客户端和服务器,然后在 Visual Studio Code 或您选择的任何代码编辑器中打开它。

制作目录

文件夹结构

现在,我们将构建一个 MongoDB 数据库,使用 Node 和 Express 设置服务器,创建一个数据库模式来代表我们的项目案例研究应用程序,并设置 API 路由,以便使用 npm 和相应的包从数据库中创建、读取、更新和删除数据和信息。因此,打开命令提示符并导航到服务器上的目录,然后运行下面的代码。

npm init -y
Enter fullscreen mode Exit fullscreen mode

设置我们的 package.json 文件

在终端中执行以下命令来安装依赖项。

npm install cors dotenv express mongoose nodemon body-parser
Enter fullscreen mode Exit fullscreen mode

依赖项

安装依赖项

安装依赖项后,“package.json”文件应如下所示。

包 Json

另外,请记住更新脚本。

脚本标签

现在转到您的服务器目录并在那里创建一个 index.js 文件。

设置index.js

  • 导入 express 模块。

  • 导入并配置dotenv模块

  • 导入 CORS 模块

  • 使用 express() 启动我们的应用程序。

//index.js
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')

// dotenv config
require('dotenv').config();

// app config
const app = express();
Enter fullscreen mode Exit fullscreen mode

现在,我们可以在该应用实例上使用所有其他方法了。让我们从基础知识和最基本的设置开始。别忘了也设置端口。

const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')

// dotenv config
require('dotenv').config();

// app and port config
const app = express();
const port = process.env.PORT || 4000;

// middlewares
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
app.use(bodyParser.json());
Enter fullscreen mode Exit fullscreen mode

设置MongoDB云集群

MongoDB 是一个开源、跨平台、面向文档的数据库。MongoDB 是一个 NoSQL 数据库,具有可选的模式,可将数据存储为类似 JSON 的文档。2018 年 10 月 16 日之前的所有版本均遵循 AGPL 许可证分发。SSPL 许可证 v1 适用于 2018 年 10 月 16 日之后发布的所有版本,包括针对旧版本的错误修复。

要设置和启动您的 MongoDB 集群,请按照下面提到的文章中概述的完全相同的步骤进行操作。

https://aviyel.com/post/1304

现在创建一个单独的数据库文件夹,并在其中创建另一个 index.js 文件。在其中导入 mongoose 库并创建一个字符串,只需粘贴复制的 mongo DB 连接 URL 或直接粘贴环境变量的链接即可。现在,在 Mongo DB 云图集 URL 链接中,输入您的用户名和密码,确保删除所有括号并输入您自己的凭据。最后,我们将使用 mongoose 连接到我们的数据库,因此输入 mongoose.connect(),它是一个具有两个不同参数的函数。第一个是 MONGO_DB_URL,第二个是具有两个不同选项的对象。第一个是 useNewUrlParser,我们将其设置为 true,第二个是 useUnifiedTopology,我们也将其设置为 true。这些对象不是必需的,但我们会在控制台上看到一些错误或警告。接下来,让我们链接 a.then() 和 .catch(),因为这将返回一个承诺,所以在 .then() 内部将调用应用程序并调用 listen,它有两个参数,第一个是 PORT,第二个是回调函数,如果我们的应用程序成功连接,最后,如果与数据库的连接不成功,我们将简单地在控制台记录我们的错误消息并最终导出该数据库。

const mongoose = require('mongoose');
require('dotenv').config();


mongoose.connect(process.env.MONGO_DB_URL, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
}).catch(e => {
    console.error('Error while connecting to the database', e.message)
})

const Database = mongoose.connection

module.exports = Database;
Enter fullscreen mode Exit fullscreen mode

将 mongodb+srv 插入到 .env 文件中。

PORT=4000
MONGO_DB_URL=mongodb+srv://pramit:<password>@cluster0.yxjll.mongodb.net/TakeNote?retryWrites=true&w=majority
Enter fullscreen mode Exit fullscreen mode

这就是全部了;我们已经成功创建了数据库。因此,让我们将其导入到我们的主根 index.js 文件中,并将我们的数据库与服务器实际连接起来。

const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')

// importing database 
const database = require('./database')

// dotenv config
require('dotenv').config();

// app and port config
const app = express();
const port = process.env.PORT || 4000;

// middlewares
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
app.use(bodyParser.json());

// DB connection
database.on('error', console.error.bind(console, 'MongoDB failed to connect'))

// listening to port
app.listen(port, () => console.log(`Currently server is running at http://localhost:${port}`))
Enter fullscreen mode Exit fullscreen mode

现在我们已经成功将服务器连接到数据库。

现在我们已经成功连接到数据库,让我们开始构建后端应用程序的路由。为此,我们需要在服务器目录上创建一个名为 routes 的新文件夹。我们将在 routes 文件夹中创建一个名为 notes-routes.js 的文件。

路线

首先,将笔记路由导入到 index.js 文件中。现在,我们可以使用 Express 中间件将笔记连接到我们的应用程序。最终,你的根 index.js 文件应该如下所示。

// index.js
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')

// importing database
const database = require('./database')
// importing routes
const noteRouter = require('./routes/note-routes')

// dotenv config
require('dotenv').config();

// app and port config
const app = express();
const port = process.env.PORT || 4000;

// middlewares
app.use(bodyParser.urlencoded({
    extended: true
}));
app.use(cors());
app.use(bodyParser.json());

// DB connection
database.on('error', console.error.bind(console, 'MongoDB failed to connect'))

app.use('/', noteRouter)

// listening to port
app.listen(port, () => console.log(`Currently server is running at http://localhost:${port}`))
Enter fullscreen mode Exit fullscreen mode

我们将路由和控制器文件夹分开。但首先,让我们创建一个笔记模型。创建一个名为 models 的文件夹,并在其中创建一个名为 noteSchema.js 的文件,并将以下代码粘贴到每个文件中。
文件夹结构现在应如下所示。

注释Schema

// models/noteSchema.js
const mongoose = require('mongoose')
const Schema = mongoose.Schema

const Note = new Schema({
    note: {
        type: String,
        required: true
    },
}, {
    timestamps: true
}, )

module.exports = mongoose.model('notes', Note)
Enter fullscreen mode Exit fullscreen mode

现在我们可以开始添加我们的路线和控制器。

// routes/note-rotes.js
const express = require('express')

const noteController = require('../controllers/noteControllers')

const router = express.Router()


router.post('/', noteController.createItem)
router.get('/', noteController.getNotes)

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

现在创建一个控制器文件夹,并在其中创建一个名为 noteControllers 的文件,并在其中创建两个名为 createItem 和 getNotes 的控制器

导入注释模式

const Note = require('../models/noteSchema')
Enter fullscreen mode Exit fullscreen mode

创建笔记

createItem = (req, res) => {

    const body = req.body

    if (!body) {
        return res.status(400).json({
            success: false,
            error: 'Please!! enter a item',
        })
    }
    const note = new Note(body)

    if (!note) {
        return res.status(400).json({
            success: false,
            error: err
        })
    }
    note.save().then(() => {
            return res.status(200).json({
                success: true,
                id: note._id,
                message: 'Cheers!! Note is Created',
            })
        })
        .catch(error => {
            return res.status(400).json({
                error,
                message: 'Error!! while creating note',
            });
        });
};
Enter fullscreen mode Exit fullscreen mode

获取所有笔记

getNotes = async(req, res) => {
    await Note.find({}, (err, notes) => {
        if (err) {
            return res.status(400).json({
                success: false,
                error: err
            })
        }
        if (!notes.length) {
            return res
                .status(404)
                .json({
                    success: false,
                    error: `Sorry, Item not found`
                })
        }
        return res.status(200).json({
            success: true,
            data: notes
        })
    }).catch(err => console.log(err))
}
Enter fullscreen mode Exit fullscreen mode

最后,你的控制器应该类似于以下内容

//controllers/noteControllers.js
const Note = require('../models/noteSchema')

createItem = (req, res) => {

    const body = req.body

    if (!body) {
        return res.status(400).json({
            success: false,
            error: 'Please!! enter a item',
        })
    }
    const note = new Note(body)

    if (!note) {
        return res.status(400).json({
            success: false,
            error: err
        })
    }
    note.save().then(() => {
            return res.status(200).json({
                success: true,
                id: note._id,
                message: 'Cheers!! Note is Created',
            })
        })
        .catch(error => {
            return res.status(400).json({
                error,
                message: 'Error!! while creating note',
            });
        });
};


getNotes = async (req, res) => {
    await Note.find({}, (err, notes) => {
        if (err) {
            return res.status(400).json({
                success: false,
                error: err
            })
        }
        if (!notes.length) {
            return res
                .status(404)
                .json({
                    success: false,
                    error: `Sorry, Item not found`
                })
        }
        return res.status(200).json({
            success: true,
            data: notes
        })
    }).catch(err => console.log(err))
}

module.exports = {
    createItem,
    getNotes
}
Enter fullscreen mode Exit fullscreen mode

重新启动服务器后,您应该会看到类似这样的内容:

服务器正在运行


使用 React 设置前端

让我们从前端开始,使用 React 来构建它。如果您的计算机上当前尚未安装 Node.js,则首先需要安装它。因此,请访问 Node.js 官方网站并获取最新版本。要使用 Node 包管理器(也称为 NPM),您需要 Node.js。现在打开您最喜欢的代码编辑器并导航到客户端文件夹。我将使用 Visual Studio Code。接下来,在集成终端中输入 npx create-react-app。此命令将在当前目录中构建一个名为 client 的客户端应用程序。

反应应用程序

有一篇单独的文章,你可以从中了解有关 react.js 的所有知识

https://aviyel.com/post/1190

现在您已经安装并清理了 react-boilerplate,是时候在其中安装一些软件包了。因此,请将以下命令复制并粘贴到您的终端中。

npm i axios moment react-router-dom prop-types
Enter fullscreen mode Exit fullscreen mode

依赖项安装

安装所有这些软件包后,客户端的 packge.json 文件应如下所示:

json 包

在安装了项目的所有依赖项之后,让我们在 components 文件夹内构建四个单独的文件夹/组件,并将其命名为 Card , CardItemList , Navbar 和 SearchForm ,同时让我们创建一个名为 pages 的文件夹,并在其中创建两个旧文件夹,分别将其命名为 RepoSearch 和 TakeNote。

添加所有组件和页面后,您的文件和文件夹结构应该看起来像这样。

文件夹结构

现在转到您的 app.js 文件并从 react-router-dom 和样式导入路由器,以及所有组件,并对代码进行必要的更改,如下所示。

// app.js
import React from "react";
import { BrowserRouter, Route } from "react-router-dom";

import Navigation from "./components/Navbar";
import RepoSearch from "./pages/RepoSearch/RepoSearch";
import TakeNote from "./pages/TakeNote/TakeNote";

const App = () => {
  return (
    <BrowserRouter>
      <Navigation />
      <Route path="/" exact component={RepoSearch} />
      <Route path="/note" exact component={TakeNote} />
    </BrowserRouter>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

然后转到卡片组件并为每个获取的 GitHub 项目创建一个类似卡片的结构。

// components/card
import React from "react";
import "./Card.css";
import moment from "moment";

const Card = (props) => {
  return (
    <div className="container__cardbox">
      <div className="card-body">
        <div className="card-title">
          <a href={props.link} target="_blank" rel="noreferrer">
            {props.title}
          </a>
        </div>
        <div className="card-description">{props.description} 📖</div>
        <div className="card-description">Total Forks: {props.forks} 🍴</div>
        <div className="card-description">Total Stars: {props.stars} </div>
        <div className="card-description">
          Last Updated: {moment(`${props.updatedAt}`).fromNow()} 
        </div>
        {/* <div className="card-description">License Name: {(props.licenseName === "Other" & null ) ? "Other License" : props.licenseName} 📜</div> */}
        <div className="card-description">
          Total Watchers: {props.watchCount} 👀
        </div>
        <div className="card-description">
          Open Issues: {props.openIssuesCount} 🏷️
        </div>
        <div className="card-description">
          Repo Size: {props.repoSize} KB ⚖️
        </div>
        <img className="card-image" src={props.image} alt={props.title} />
      </div>
    </div>
  );
};

export default Card;
Enter fullscreen mode Exit fullscreen mode

并且不要忘记在其中创建一个 card.css 文件并向其中添加以下样式

.container__cardbox {
  flex: 1;
  /* flex-grow: 4; */
  flex-basis: 15%;
  margin: 15px;
  /* border: solid 2px #383636; */
  border-radius: 25px;
  /* display: flex; */
  flex-flow: row wrap;
}

.card-body {
  padding: 10px;
  border-radius: 20px;
  background: white;
}

.card-title {
  font-size: 25px;
  text-align: left;
}

.card-description {
  font-size: 12px;
  margin: 4px;
  text-align: center;
}

.card-image {
  width: 20%;
  margin-top: -130px;
}
Enter fullscreen mode Exit fullscreen mode

然后,在 CardItemList 下,导入 Card 组件并为其提供所有适当的道具,以便它可以显示卡片组件本身内的所有元素。

//components/CardItemList
import React from "react";
import Card from "../Card";

const CardItemList = (props) => {
  return (
    <div className="container__carditemlist">
      {props.items.map((item) => (
        <Card
          key={item.id}
          link={item.html_url}
          title={item.full_name}
          description={item.description}
          image={item.owner.avatar_url}
          forks={item.forks_count}
          stars={item.stargazers_count}
          updatedAt={item.updated_at}
          watchCount={item.watchers_count}
          openIssuesCount={item.open_issues_count}
          repoSize={item.size}
        />
      ))}
    </div>
  );
};

export default CardItemList;
Enter fullscreen mode Exit fullscreen mode

再次不要忘记在其中创建一个 CardItemList.css 文件并向其中添加以下样式

.container__carditemlist {
  display: flex;
  flex-wrap: wrap;
}
Enter fullscreen mode Exit fullscreen mode

因此,在开发搜索表单之前,我们先来处理导航栏部分。转到导航栏组件并将以下代码粘贴到其中。

// components/Navbar
import React from "react";
import { Link } from "react-router-dom";
import "./Navbar.css";

const Navbar = () => {
  return (
    <div className="container__navbar">
      <div className="navbar-title">Github Search</div>
      <ul className="navbar-menu">
        <li>
          <Link to="/">Search-Projects</Link>
        </li>
        <li>
          <Link to="/note">Take-Note</Link>
        </li>
      </ul>
      <div className="navbar-menu"></div>
    </div>
  );
};

export default Navbar;
Enter fullscreen mode Exit fullscreen mode

记得在其中创建一个 Navbar.css 文件并对其应用以下样式。

@import url("https://fonts.googleapis.com/css?family=Raleway:400,400i,800");
 .container__navbar {
     display: flexbox;
     align-items: center;
     background: url("../../assets/gifs/navBack.gif") no-repeat center center fixed;
     -webkit-background-size: cover;
     -moz-background-size: cover;
     -o-background-size: cover;
     background-size: cover;
    /* background-color: transparent;
     */
     padding: 25px;
     width: 100%;
     margin-bottom: 20px;
}
 @media only screen and (max-width: 900px) {
     .container__navbar {
         display: flexbox;
         align-items: center;
         background: url("../../assets/gifs/navBack.gif") no-repeat center center fixed;
         -webkit-background-size: cover;
         -moz-background-size: cover;
         -o-background-size: cover;
         background-size: cover;
        /* background-color: transparent;
         */
         padding: 25px;
         width: 100rem;
         margin-bottom: 20px;
    }
}
 .navbar-title {
     color: transparent;
     font-size: 28px;
     margin-bottom: -50px;
     text-align: right;
}
 .navbar-menu {
     border-radius: 25px;
     height: -webkit-fit-content;
     height: -moz-fit-content;
     height: fit-content;
     display: inline-flex;
     background-color: rgba(0, 0, 0, 0.4);
     -webkit-backdrop-filter: blur(10px);
     backdrop-filter: blur(10px);
     align-items: center;
     padding: 0 20px;
     margin: 50px 0 0 0;
}
 .navbar-menu li {
     list-style: none;
     color: white;
     font-family: sans-serif;
     font-weight: bold;
     padding: 12px 60px;
     margin: 0 8px;
     position: relative;
     cursor: pointer;
     white-space: nowrap;
}
 .navbar-menu li::before {
     content: " ";
     position: absolute;
     top: 0;
     left: 0;
     height: 100%;
     width: 100%;
     z-index: -1;
     transition: 0.6s;
     border-radius: 25px;
}
 .navbar-menu li:hover {
     color: black;
}
 .navbar-menu li:hover::before {
     background: linear-gradient(to bottom, #e8edec, #d2d1d3);
     box-shadow: 0px 3px 20px 0px black;
     transform: scale(1.2);
}
 @media only screen and (max-width: 1000px) {
     .navbar-menu {
         border-radius: 25px;
         height: -webkit-fit-content;
         height: -moz-fit-content;
         height: fit-content;
         display: inline-flex;
         background-color: rgba(0, 0, 0, 0.4);
         -webkit-backdrop-filter: blur(10px);
         backdrop-filter: blur(10px);
         align-items: center;
         padding: 0 0px;
         margin: 50px 0 0 0;
    }
     .navbar-menu li {
         list-style: none;
         color: white;
         font-family: sans-serif;
         font-weight: bold;
         padding: 12px 10px;
         margin: 0 1px;
         position: relative;
         cursor: pointer;
         white-space: nowrap;
    }
     .navbar-menu li::before {
         content: " ";
         position: absolute;
         top: 0;
         left: 0;
         height: 100%;
         width: 100%;
         z-index: -1;
         transition: 0.6s;
         border-radius: 25px;
    }
     .navbar-menu li:hover {
         color: black;
    }
     .navbar-menu li:hover::before {
         background: linear-gradient(to bottom, #e8edec, #d2d1d3);
         box-shadow: 0px 3px 20px 0px black;
         transform: scale(1.2);
    }
}
Enter fullscreen mode Exit fullscreen mode

最后,让我们开始研究SearchForm

// components/SearchForm
import React, { useState } from "react";
import "./SearchForm.css";

const SearchForm = (props) => {
  const [value, setValue] = useState("");

  const submitSearchValue = () => {
    setValue("");
    props.onSubmit(value);
  };

  return (
    <div>
      <input
        className="search-input"
        type="text"
        placeholder={props.placeholder}
        value={value}
        onChange={(event) => setValue(event.target.value)}
      />
      <label htmlFor="name" className="search-label">
        Search Project
      </label>
      <button
        className="search-button"
        type="submit"
        onClick={() => submitSearchValue()}
      >
        {props.buttonText}
      </button>
    </div>
  );
};

export default SearchForm;
Enter fullscreen mode Exit fullscreen mode

记得在 SearchForm.css 文件中包含以下样式

@import url("https://fonts.googleapis.com/css2?family=Finger+Paint&display=swap");


.search-button {
  background-image: linear-gradient(
    to right,
    #02aab0 0%,
    #00cdac 51%,
    #02aab0 100%
  );
}
.search-button {
  margin: 0 auto;
  padding: 10px 100px;
  margin-top: 0px;
  text-align: center;
  text-transform: uppercase;
  transition: 0.5s;
  background-size: 200% auto;
  color: white;
  box-shadow: 0 0 20px #eee;
  border-radius: 10px;
  display: block;
  outline: none;
}

.search-button:hover {
  background-position: right center; /* change the direction of the change here */
  color: #fff;
  text-decoration: none;
}

.search-label {
  color: white;
  font-family: "Finger Paint", cursive;
  font-size: 1.2rem;
  margin-left: 2rem;
  margin-top: 0.2rem;
  display: block;
  transition: all 0.3s;
  transform: translateY(0rem);
}

.search-input {
  color: #333;
  font-size: 1.2rem;
  margin: 0 auto;
  padding: 1rem 0.5rem;
  border-radius: 0.6rem;
  background-color: rgb(255, 255, 255);
  border: none;
  width: 50rem;
  display: block;
  border-bottom: 1rem solid transparent;
  transition: all 0.3s;
  outline:none;
}

@media only screen and (max-width: 900px) {
  .search-input {
    color: #333;
    font-size: 1.2rem;
    margin: 0 auto;
    padding: 1rem 0.5rem;
    border-radius: 0.6rem;
    background-color: rgb(255, 255, 255);
    border: none;
    width: 100%;
    display: block;
    border-bottom: 1rem solid transparent;
    transition: all 0.3s;
  }
}

.search-input:placeholder-shown + .search-label {
  opacity: 0;
  color: white;
  visibility: hidden;
  -webkit-transform: translateY(-4rem);
  transform: translateY(-4rem);
}
Enter fullscreen mode Exit fullscreen mode

现在我们已经成功将组件集成到应用程序中,接下来该指定页面了。在 pages 目录中,创建一个 RepoSearch 文件夹,并创建两个文件:RepoSearch.js 和 RepoSearch.css。只需将 SearchForm 和 CardItemList 组件导入 RepoSearch 页面,然后在 RepoSearch 中构建一个名为 repos 的 useState 钩子,并以空数组作为初始值。

这个钩子使我们能够将状态集成到我们的函数式组件中。与类组件中的状态不同,useState() 不适用于对象值。如有必要,我们可以直接使用原语,并为多个变量创建多个 React 钩子。const [state, setState] = useState(initialState);

还要记住,React 中的 hooks 必须始终在函数顶部声明。这也有助于在组件的所有渲染之间保存状态。最后,让我们开发 searchRepository 函数,它使用免费的 GitHub API 获取所有项目信息,并直接返回 SearchForm 和 CardItemList 组件,并将 searchRepository 函数作为 prop 传递给 SearchForm 组件,并将 repos 传递给 CardItemLists 组件。

//pages/RepoSearch
import React, { useState } from "react";
import axios from "axios";

import SearchForm from "../../components/SearchForm";
import CardItemList from "../../components/CardItemList";

import "./RepoSearch.css";

const RepoSearch = () => {
  const [repos, setRepos] = useState([]);

  const searchRepository = (searchQuery) => {
    setRepos([]);
    axios
      .get(
        `https://api.github.com/search/repositories?q=${searchQuery}{&page,per_page,sort,order}`
      )
      .then((result) => setRepos(result.data.items));
  };

  return (
    <div className="container__RepoSearch">
      <SearchForm
        placeholder="Search Projects."
        buttonText="Search"
        onSubmit={(value) => searchRepository(value)}
      />
      <CardItemList items={repos} />
    </div>
  );
};

export default RepoSearch;
Enter fullscreen mode Exit fullscreen mode

不要忘记也应用以下样式。

.container__RepoSearch {
  display: flex;
  flex-direction: column;
  align-items: center;
}
Enter fullscreen mode Exit fullscreen mode

最后,让我们在应用程序中构建一个生成笔记的功能。为此,需要构建两个状态:笔记和项目。

const [notes,setNotes] = useState([]);
const [items,setItems] = useState("");
Enter fullscreen mode Exit fullscreen mode

然后返回代码并实现 useEffect 功能。通过使用此 Hook,你告诉 React 你的组件需要在渲染后执行某些操作。React 会记住你传递的函数(我们将其称为“效果”),并在执行 DOM 更新后调用它。为此,我们设置了文档标题,但我们也可以执行数据获取或调用其他一些命令式 API。将 useEffect() 放置在组件内部,我们可以直接从效果中访问 c​​ount 状态变量(或任何 props)。我们不需要特殊的 API 来读取它——它已经在函数作用域中了。Hook 拥抱 JavaScript 闭包,并避免在 JavaScript 已经提供解决方案的情况下引入特定于 React 的 API。useEffect() Hook 有点类似于我们所知道的类组件的生命周期方法。它在组件的每次渲染(包括初始渲染)后运行。因此,它可以被认为是 componentDidMount、componentDidUpdate 和 componentWillUnmount 的组合。如果我们想要控制 effect 的运行时间(仅在初始渲染时,或仅在特定状态变量发生变化时),我们可以将依赖项传递给 effect 来实现。此钩子还提供了一个清理选项,允许在组件销毁前清理资源。effect 的基本语法:useEffect(didUpdate);

这里,didUpdate 是一个执行突变、订阅、计时器、日志记录等操作的函数。它将在组件渲染到屏幕后以及随后每次完成渲染时触发。

useEffect(() => {
  axios.get("http://localhost:4000").then((response) => {
    let data = [];
    for (var i = 0; i < response.data.data.length; i++) {
      data.push(response.data.data[i].note);
    }
    setNotes(data);
  });
}, []);
Enter fullscreen mode Exit fullscreen mode

并在实现 useEffect 功能后创建两个名为 clickHandler 和 changeHandler 的函数。

const changeHandler = (e) => {
  setItems(e.target.value);
};
Enter fullscreen mode Exit fullscreen mode


const clickHandler = async (e) => {
  axios({
    method: "post",
    url: "http://localhost:4000",
    data: {
      note: items,
    },
  })
    .then(() => {
      setItems("");
    })
    .then(() => {
      window.location.reload(false);
    });
};
Enter fullscreen mode Exit fullscreen mode

构建完所需的一切之后,只需返回以下语句。

return (
  <div className="conatiner__back">
    <input
      className="todo-input"
      placeholder="Organize and keep track of newly explored awesome projects."
      type="text"
      onChange={changeHandler}
    />
    <button className="todo-button" type="submit" onClick={clickHandler}>
       Add Notes
    </button>
    <small style={{ color: "white", fontSize: "10px" }}>
      * all notes will get stored inside MongoDB
    </small>
    <div className="notes__layout">
      <ol className="gradient-list">
        {notes.map((note) => (
          <li key={note._id}>{note}</li>
        ))}
      </ol>
    </div>
  </div>
);
Enter fullscreen mode Exit fullscreen mode

您的最终代码应该是这样的。

// pages/TakeNote.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import "./TakeNote.css";

const TakeNote = () => {
  const [notes, setNotes] = useState([]);
  const [items, setItems] = useState("");
  const changeHandler = (e) => {
    setItems(e.target.value);
  };
  const clickHandler = async (e) => {
    axios({
      method: "post",
      url: "http://localhost:4000",
      data: {
        note: items,
      },
    })
      .then(() => {
        setItems("");
      })
      .then(() => {
        window.location.reload(false);
      });
  };

  useEffect(() => {
    axios.get("http://localhost:4000").then((response) => {
      let data = [];
      for (var i = 0; i < response.data.data.length; i++) {
        data.push(response.data.data[i].note);
      }
      setNotes(data);
    });
  }, []);

  return (
    <div className="conatiner__back">
      <input
        className="todo-input"
        placeholder="Organize and keep track of newly explored awesome projects."
        type="text"
        onChange={changeHandler}
      />
      <button className="todo-button" type="submit" onClick={clickHandler}>
         Add Notes
      </button>
      <small style={{ color: "white", fontSize: "10px" }}>
        * all notes will get stored inside MongoDB
      </small>
      <div className="notes__layout">
        <ol className="gradient-list">
          {notes.map((note) => (
            <li key={note._id}>{note}</li>
          ))}
        </ol>
      </div>
    </div>
  );
};

export default TakeNote;
Enter fullscreen mode Exit fullscreen mode

最后,在 TakeNote 内部创建一个 TakeNote.css 文件,并向其中添加下面列出的样式。

// TakeNote.css
@import url("https://fonts.googleapis.com/css2?family=Finger+Paint&display=swap");

.conatiner__back {
  text-align: center;
  background-color: transparent;
}

.todo-button {
  background-image: linear-gradient(
    to right,
    #02aab0 0%,
    #00cdac 51%,
    #02aab0 100%
  );
}
.todo-button {
  margin: 0 auto;
  padding: 10px 100px;
  margin-top: 10px;
  text-align: center;
  text-transform: uppercase;
  transition: 0.5s;
  background-size: 200% auto;
  color: white;
  box-shadow: 0 0 20px #eee;
  border-radius: 10px;
  display: block;
  outline: none;
}

.todo-button:hover {
  background-position: right center;
  color: #fff;
  text-decoration: none;
}

.todo-input {
  color: white;
  font-size: 1.2rem;
  font-family: "Finger Paint", cursive;
  margin: 0 auto;
  padding: 1rem 0.5rem;
  border-radius: 0.6rem;
  /* background-color: rgb(255, 255, 255); */
  background: url("../../assets/gifs/inputBack.gif");
  -webkit-background-size: cover;
  -moz-background-size: cover;
  -o-background-size: cover;
  background-size: cover;
  border: none;
  width: 50rem;
  display: block;
  border-bottom: 1rem solid transparent;
  transition: all 0.3s;
  outline: none;
}

@media only screen and (max-width: 900px) {
  .todo-input {
    color: #333;
    font-size: 1.2rem;
    margin: 0 auto;
    padding: 1rem 0.5rem;
    border-radius: 0.6rem;
    background-color: rgb(255, 255, 255);
    border: none;
    width: 100%;
    display: block;
    border-bottom: 1rem solid transparent;
    transition: all 0.3s;
  }
}

/* ------------------------------------------------- */

ol.gradient-list > li::before,
ol.gradient-list > li {
  box-shadow: 0.25rem 0.25rem 0.6rem rgba(0, 0, 0, 0.05),
    0 0.5rem 1.125rem rgba(75, 0, 0, 0.05);
}

/*** STYLE ***/
*,
*:before,
*:after {
  box-sizing: border-box;
}

.notes__layout {
  display: block;
  margin: 0 auto;
  max-width: 40rem;
  padding: 1rem;
}

ol.gradient-list {
  list-style: none;
  margin: 1.75rem 0;
  padding-left: 1rem;
}
ol.gradient-list > li {
  background: white;
  text-align: left;
  font-family: "Finger Paint", cursive;
  border-radius: 0 0.5rem 0.5rem 0.5rem;
  counter-increment: gradient-counter;
  margin-top: 2rem;
  min-height: 3rem;
  border-radius: 20px;
  padding: 1rem 1rem 1rem 3rem;
  position: relative;
}
ol.gradient-list > li::before,
ol.gradient-list > li::after {
  background: linear-gradient(90deg, #83e4e2 0%, #a2ed56 100%);
  border-radius: 5rem 5rem 0 5rem;
  content: "🔖";
  height: 2.5rem;
  left: -1rem;
  overflow: hidden;
  position: absolute;
  top: -2rem;
  width: 3rem;
}
ol.gradient-list > li::before {
  align-items: flex-end;
  content: counter(gradient-counter);
  color: #1d1f20;
  display: flex;
  font: 1000 1.5em/1 "Montserrat";
  justify-content: center;

  justify-content: flex-end;
  padding: 0.125em 0.25em;
  z-index: 1;
}
ol.gradient-list > li:nth-child(10n + 1):before {
  background: linear-gradient(
    135deg,
    rgba(162, 237, 86, 0.2) 0%,
    rgba(253, 220, 50, 0.2) 100%
  );
}
ol.gradient-list > li:nth-child(10n + 2):before {
  background: linear-gradient(
    135deg,
    rgba(162, 237, 86, 0.4) 0%,
    rgba(253, 220, 50, 0.4) 100%
  );
}
ol.gradient-list > li:nth-child(10n + 3):before {
  background: linear-gradient(
    135deg,
    rgba(162, 237, 86, 0.6) 0%,
    rgba(253, 220, 50, 0.6) 100%
  );
}
ol.gradient-list > li:nth-child(10n + 4):before {
  background: linear-gradient(
    135deg,
    rgba(162, 237, 86, 0.8) 0%,
    rgba(253, 220, 50, 0.8) 100%
  );
}
ol.gradient-list > li:nth-child(10n + 5):before {
  background: linear-gradient(135deg, #a2ed56 0%, #fddc32 100%);
}
ol.gradient-list > li:nth-child(10n + 6):before {
  background: linear-gradient(
    135deg,
    rgba(162, 237, 86, 0.8) 0%,
    rgba(253, 220, 50, 0.8) 100%
  );
}
ol.gradient-list > li:nth-child(10n + 7):before {
  background: linear-gradient(
    135deg,
    rgba(162, 237, 86, 0.6) 0%,
    rgba(253, 220, 50, 0.6) 100%
  );
}
ol.gradient-list > li:nth-child(10n + 8):before {
  background: linear-gradient(
    135deg,
    rgba(162, 237, 86, 0.4) 0%,
    rgba(253, 220, 50, 0.4) 100%
  );
}
ol.gradient-list > li:nth-child(10n + 9):before {
  background: linear-gradient(
    135deg,
    rgba(162, 237, 86, 0.2) 0%,
    rgba(253, 220, 50, 0.2) 100%
  );
}
ol.gradient-list > li:nth-child(10n + 10):before {
  background: linear-gradient(
    135deg,
    rgba(162, 237, 86, 0) 0%,
    rgba(253, 220, 50, 0) 100%
  );
}
ol.gradient-list > li + li {
  margin-top: 2rem;
}
Enter fullscreen mode Exit fullscreen mode

该应用程序的完整源代码可在此处获取。

https://github.com/pramit-marattha/project-case-study-mern-app


主要文章可在此处查看 => https://aviyel.com/post/1419

编码愉快!!

如果您是项目维护者、贡献者或只是开源爱好者,请关注@aviyelHQ或在 Aviyel 上注册以获得早期访问权限。

加入 Aviyel 的 Discord => Aviyel 的世界

推特 =>[ https://twitter.com/AviyelHq ]

文章来源:https://dev.to/aviyel/building-a-project-case-study-app-using-the-absolute-power-of-mern-stack-15ak
PREV
React 最佳实践
NEXT
您在 Next.js read-env 中以错误的方式读取环境变量