2022 年现代 React 开发者全书

2025-05-25

2022 年现代 React 开发者全书

介绍

本课程旨在帮助您在 2022 年成为一名全面的现代 React 开发者。本课程仅涵盖ReduxGraphQLReact Native这三个主题,这些主题可能会在未来的课程中涵盖。TypeScript 将成为本课程的主要编程语言,但如果你已经了解 JavaScript,那么你应该会发现它很容易理解,因为语法并没有太大区别。

我们将构建一个超级基本的 Twitter 克隆,它具有发布、阅读和删除推文的 CRUD 功能。

本课程将为你提供成为全栈软件开发人员所需的技能和知识。你将学习:

先决条件

确保已设置好开发环境,并安装了简介中列出的所有工具/库。我使用的是 Mac,因此我提到的一些工具仅适用于 macOS。不过,如果您使用 Windows 或 Linux,您应该能够找到其他替代方案,并能够继续操作。

MongoDB 设置

您需要安装以下工具来使用 MongoDB NoSQL 数据库。MongoDB Compass 是一个用于操作 MongoDB 数据库的 GUI。mongosh 是一个 MongoDB shell,用于通过命令行操作 MongoDB 数据库。

MongoDB 指南针
mongosh

PostgreSQL 设置

您需要安装以下工具才能使用 PostgreSQL 数据库。Postgres.app 是一款用于管理 PostgreSQL 数据库的应用程序。Pgcli 是 Postgres 的命令行界面,带有自动补全和语法高亮功能。它是 PostgreSQL 版的 mongosh。

在使用 GUI 操作 PostgreSQL 数据库时,我首选 Valentina Studio。它是一款非常棒的工具,因为它甚至可以连接到 MongoDB 和 MySQL 数据库。不过也有其他选择,比如PgAdmin,所以只要你觉得用得顺手就行。

Postgres.app
Pgcli
Valentina Studio

设置后端

在本节中,您将学习如何使用 Express.js 和 Nest.js 设置 Node 后端。这两个框架都将使用不同的端点连接到 MongoDB 和 PostgreSQL 数据库。此外,当我们将 MongoDB 和 PostgreSQL 数据库放入 Docker 容器中时,您还将学习一些 DevOps 知识。

Docker 基本上赋予了开发人员将应用程序打包到容器中的能力。因此,本质上,您只需在 Docker 容器中创建一个数据库,任何外部应用程序都可以连接到该数据库。通过这种设置,您甚至无需在本地计算机上安装或设置数据库。您可以将所有内容都运行在 Docker 容器中,并且此设置在任何计算机上都能以完全相同的方式运行。

我认为这是本地安装的绝佳替代方案,并且通过这些知识,它为您提供了另一种与数据库交互的方式。此工作流程不需要进行大量设置,您可以使用 GUI 或命令行与 Docker 容器内的数据库进行交互,就像在本地或在线数据库一样。

本地数据库设置

Pgcli 命令
mongosh 命令

MongoDB 本地

打开您的命令行工具我将使用Hyper并运行以下命令连接到您的本地 MongoDB 安装。

mongosh
Enter fullscreen mode Exit fullscreen mode

首先运行此命令,它将显示您正在使用的数据库。它应该返回test默认数据库。

db
Enter fullscreen mode Exit fullscreen mode

现在运行下面的命令,它将显示您当前已创建的数据库。

show dbs;
Enter fullscreen mode Exit fullscreen mode

接下来运行命令创建一个名为twitter的数据库。

use twitter;
Enter fullscreen mode Exit fullscreen mode

最后使用下面的命令创建一个集合,当您在命令行中再次使用该命令时,您应该看到您创建的show dbs;数据库。twitter

db.createCollection('contents');
Enter fullscreen mode Exit fullscreen mode

最后,我们将添加一些初始数据,并将下面的代码复制粘贴到命令行中。插入数据后,在命令行中运行此命令db.contents.find().pretty(),即可在表格中看到数据。

db.contents.insertMany([ {tweet: "Hello World!", img: ""}, {tweet: "Content creation and programming are basically full time jobs. I have enough projects and work to keep me busy for years. Working in tech is definitely going to entertain you for a long time which is why so many people want to transition into this field.", img: ""}, {tweet: "JavaScript developers are forever in high demand", img: ""} ])
Enter fullscreen mode Exit fullscreen mode

如果您在计算机上打开 MongoDB Compass 应用程序,并使用连接字符串连接到本地安装,mongodb://localhost:27017那么您应该能够看到所有数据库,包括我们刚刚在 GUI 中创建的数据库。现在,您已设置好使用命令行或 GUI 与数据库交互。

https://res.cloudinary.com/d74fh3kw/image/upload/c_scale,w_800/v1650544114/mongodb-compass-twitter_hrnwiy.jpg

PostgreSQL 本地

在执行任何操作之前,请检查您的 PostgreSQL 数据库是否在本地运行。如果它未运行,则在运行命令时可能会在命令行中收到错误pgcli。在 macOS 上,我将使用 Postgres.app,因此请在您的计算机上运行它,然后它应该会显示在操作系统顶部的菜单栏中。

现在转到命令行并运行以下命令来连接到本地 PostgreSQL 安装。

pgcli
Enter fullscreen mode Exit fullscreen mode

运行以下命令将显示所有 PostgreSQL 数据库。

\l
Enter fullscreen mode Exit fullscreen mode

将下面的 SQL 查询复制并粘贴到 pgcli 命令行窗口中,以创建一个名为twitter的数据库。现在,如果您在同一个窗口中再次运行该命令,\l您应该看到所有数据库,包括我们刚刚创建的数据库。

CREATE DATABASE twitter;
Enter fullscreen mode Exit fullscreen mode

接下来我们需要在同一个窗口中连接到数据库,因此请使用下面的命令来执行此操作。

\c twitter
Enter fullscreen mode Exit fullscreen mode

接下来,我们必须创建一个表并添加一些数据,这些数据将存入数据库twitter中。将下面的 SQL 代码复制并粘贴到命令行窗口中。

CREATE TABLE contents (

id UUID DEFAULT gen_random_uuid (),

tweet VARCHAR(280) NOT NULL,

img VARCHAR(500) NOT NULL

);

INSERT INTO contents (tweet, img)

VALUES ('Hello World!', ''), ('Content creation and programming are basically full time jobs. I have enough projects and work to keep me busy for years. Working in tech is definitely going to entertain you for a long time which is why so many people want to transition into this field.', ''), ('JavaScript developers are forever in high demand', '');
Enter fullscreen mode Exit fullscreen mode

如果您在计算机上打开 Postgres.app 应用程序,您应该会看到所有数据库,包括我们刚刚创建的数据库。

https://res.cloudinary.com/d74fh3kw/image/upload/c_scale,w_800/v1650553228/postgresql-app-twitter_ygwp3z.jpg

如果您连接到 Valentina Studio 或您选择的数据库 GUI,您应该能够看到您创建的数据库。

PostgreSQL 数据库连接设置

https://res.cloudinary.com/d74fh3kw/image/upload/c_scale,w_800/v1650553476/valentina-db-twitter-connection_ktkq6v.jpg

瓦伦蒂娜工作室

https://res.cloudinary.com/d74fh3kw/image/upload/c_scale,w_800/v1650553510/valentina-db-twitter_rss8fv.jpg

Docker 数据库设置

在计算机上启动 Docker 应用程序,并针对每个数据库执行以下步骤。首先在本地计算机上创建一个名为complete-react-developer 的
文件夹 ,然后cd使用命令行进入。

MongoDB Docker

仔细检查您是否位于complete-react-developer的根文件夹中,然后运行以下命令来设置项目。

mkdir docker-twitter-mongodb
cd docker-twitter-mongodb
touch docker-compose.yml
Enter fullscreen mode Exit fullscreen mode

在代码编辑器中打开该文件夹并将以下代码添加到docker-compose.yml文件中。

docker-compose.yml

请注意 yaml 代码格式,如果缩进不正确,则会出现错误。

version: '3.9'
services:
  mongo_db:
    container_name: db_container
    image: 'mongo:latest'
    restart: always
    ports:
      - '2717:27017'
    volumes:
      - 'mongo_db:/data/db'
volumes:
  mongo_db: {}
Enter fullscreen mode Exit fullscreen mode

现在运行下面的代码来启动带有 MongoDB 数据库的 docker 容器。

docker compose up
Enter fullscreen mode Exit fullscreen mode

假设一切顺利,您应该在 Docker 容器内运行一个 MongoDB 数据库。

https://res.cloudinary.com/d74fh3kw/image/upload/c_scale,w_800/v1650622543/docker-mongodb_nf7ezr.jpg

连接到 Docker 容器内的 MongoDB 数据库

现在可以同时连接到本地 MongoDB 数据库和 MongoDB Docker 数据库,因为它们都配置为在不同的端口上运行。

本地 MongoDB 数据库位于端口 27017,因此使用以下命令连接到本地 mongodb 数据库。

mongosh --port 27017
Enter fullscreen mode Exit fullscreen mode

MongoDB Compass 的连接字符串如下。

mongodb://localhost:27017
Enter fullscreen mode Exit fullscreen mode

MongoDB Docker 数据库位于端口 2717,因此使用以下命令连接到 Docker MongoDB 数据库。

mongosh --port 2717
Enter fullscreen mode Exit fullscreen mode

MongoDB Compass 的连接字符串如下。

mongodb://localhost:2717
Enter fullscreen mode Exit fullscreen mode

现在,Twitter 有两个 MongoDB 数据库,一个在本地,一个在 Docker 容器中。让我们向数据库中添加一些数据,即使删除容器,这些数据也会保留下来。

在命令行中打开到 Docker 容器内的 MongoDB 数据库的 mongosh 连接。

mongosh --port 2717
Enter fullscreen mode Exit fullscreen mode

运行以下命令。您将创建一个名为twitter的数据库,其中包含一个名为contents的集合。然后,您将向数据库中插入一些数据。

use twitter;

db.createCollection('contents');

db.contents.insertMany([ {tweet: "Hello World!", img: ""}, {tweet: "Content creation and programming are basically full time jobs. I have enough projects and work to keep me busy for years. Working in tech is definitely going to entertain you for a long time which is why so many people want to transition into this field.", img: ""}, {tweet: "JavaScript developers are forever in high demand", img: ""} ]);
Enter fullscreen mode Exit fullscreen mode

db.contents.find().pretty();现在,当你在命令行中运行该命令时,它应该会返回你刚刚插入的数据。如果你访问 MongoDB Compass 并使用此连接字符串mongodb://localhost:2717,你应该会看到包含数据的Twitter数据库。

PostgreSQL Docker

检查您是否位于complete-react-developer的根文件夹中,然后运行以下命令来设置项目。

mkdir docker-twitter-postgresql
cd docker-twitter-postgresql
touch docker-compose.yml
mkdir sql
cd sql
touch twitter.sql
cd ..
Enter fullscreen mode Exit fullscreen mode

在代码编辑器中打开该文件夹并将以下代码添加到docker-compose.ymltwitter.sql文件中。

docker-compose.yml

请注意 yaml 代码格式,如果缩进不正确,则会出现错误。

version: '3.7'
services:
  postgres:
    image: postgres:latest
    restart: always
    environment:
      - POSTGRES_USER=twitter
      - POSTGRES_PASSWORD=twitter
      - POSTGRES_DB=twitter
    ports:
      - '5433:5432'
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
      # copy the sql script to create tables
      - ./sql/twitter.sql:/docker-entrypoint-initdb.d/twitter.sql
Enter fullscreen mode Exit fullscreen mode

twitter.sql

CREATE TABLE contents (

id UUID DEFAULT gen_random_uuid (),

tweet VARCHAR(280) NOT NULL,

img VARCHAR(500) NOT NULL

);

INSERT INTO contents (tweet, img)

VALUES ('Hello World!', ''), ('Content creation and programming are basically full time jobs. I have enough projects and work to keep me busy for years. Working in tech is definitely going to entertain you for a long time which is why so many people want to transition into this field.', ''), ('JavaScript developers are forever in high demand', '');
Enter fullscreen mode Exit fullscreen mode

现在运行下面的代码来启动带有 PostgreSQL 数据库的 Docker 容器。

docker compose up
Enter fullscreen mode Exit fullscreen mode

当你看到日志显示数据库系统已准备好接受连接时,你就知道它正在运行。你也可以在 Docker 桌面应用程序中验证这一点,如果你检查它,你应该会看到容器正在运行。

https://res.cloudinary.com/d74fh3kw/image/upload/c_scale,w_800/v1650557620/docker-postgresql_z3nl5i.jpg

我们刚刚完成了一个 PostgreSQL 数据库的设置,它将驻留在 Docker 容器中。该数据库甚至会包含一些我们创建的 SQL 脚本中预先构建的数据。

连接到 Docker 容器内的 PostgreSQL 数据库

docker-twitter-postgresqldocker-compose.yml文件夹中的文件具有5433:5432的端口映射。5433 是本地端口,5432 是 Docker 端口,这也是 Postgres 的默认端口。我这样做是为了让我们可以在本地使用端口 5432 上的 Postgres 应用,同时在端口 5433 上运行 Docker 数据库。

奇迹就在这里发生!使用下图中的连接凭证,您应该能够连接到 Docker 容器内的 PostgreSQL 数据库!

顺便一下,密码是twitter,您可以在文件里面找到凭证docker-compose.yml

https://res.cloudinary.com/d74fh3kw/image/upload/c_scale,w_800/v1650558031/docker-postgresql-db-connection_xajz8z.jpg

或者,您也可以使用pgcli以下命令在命令行中连接数据库。密码与之前一样,为twitter

pgcli -h localhost -p 5433 -u twitter -d twitter
Enter fullscreen mode Exit fullscreen mode

现在我们在 5432 端口上有一个本地 PostgreSQL 数据库,名为twitter 。在 5433 端口上还有一个 Docker PostgreSQL 数据库,名为twitter。Valentina Studio 可以同时连接到这两个数据库,您可以运行所有 SQL 查询。此外,Docker 容器内的 PostgreSQL 数据库可以持久化其数据。如果您删除正在运行的 Docker 容器,然后docker compose up再次运行该命令,一切将保持不变!

docker compose down如果需要,请使用命令停止 Docker 容器的运行。

恭喜您刚刚学习了 MongoDB、PostgreSQL 和 Docker 的基础知识!

节点后端设置

本教程将分为两部分。一部分介绍如何使用 Express.js 和 TypeScript 创建后端。另一部分介绍如何使用 Nest.js 和 TypeScript 创建后端,以便您了解两者之间的区别,并学习在 Node 中开发后端的其他方法。

因此,将有 4 个 Node 后端可供使用:

  • 后端-express-mongodb
  • 后端-express-postgresql
  • 后端-nest-mongodb
  • 后端-nest-postgresql

您需要一个 REST API 测试工具来测试各种路由和端点。以上是我最常用的 3 个工具,如果您愿意,也可以使用其他工具。

Postman
Thunder客户端
失眠

您无需创建所有这些后端,因为创建 React 前端时只需要一个后端,但这仍然是值得学习的知识。当然,您也可以只使用一个后端来连接 MongoDB 和 PostgreSQL,因为这些示例中的解释更容易理解。

快捷应用

后端 Express MongoDB

确保您位于complete-react-developer文件夹内。

运行以下命令来构建您的项目。

mkdir backend-express-mongodb
cd backend-express-mongodb
tsc --init
npm init -y
npm i express @types/express @types/node ts-node cors @types/cors mongoose @types/mongoose typescript rimraf copy-files dotenv nodemon
touch .env
mkdir src
cd src
touch app.ts
mkdir controllers models routes
touch controllers/Admin.ts
touch models/Twitter.ts
touch routes/Admin.ts
cd ..
Enter fullscreen mode Exit fullscreen mode

在代码编辑器中打开项目,转到tsconfig.json根文件夹内的文件并启用这些属性。

"rootDir": "./src" /* Specify the root folder within your source files. */,
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
"outDir": "./dist/src" /* Specify an output folder for all emitted files. */,
Enter fullscreen mode Exit fullscreen mode

下一步打开package.json文件并添加这些运行脚本。

"scripts": {

"start": "node dist/src/app.js",

"dev": "nodemon src/app.ts",

"clean": "rimraf dist/",

"build": "npm run clean && tsc && npm run copy-files",

"copy-files": "copyfiles -u 1 src/**/*.html src/**/*.css src/**/*.json dist/src"

},
Enter fullscreen mode Exit fullscreen mode
运行脚本

启动
启动脚本使用 Node 运行应用程序,当文件有更新时不会自动重新加载。

dev
dev 脚本使用 nodemon 在文件发生变化时自动重新加载并更新文件。

clean
清理脚本删除dist文件夹。

build
构建脚本删除dist文件夹,然后自动复制所有文件并将它们放回dist文件夹。

copy-files
复制文件脚本用于将文件从一个目录复制到另一个目录。

添加代码

最后将下面的代码添加到相应的文件中。

controllers/Admin.ts

import { Response, Request } from 'express';

import mongoose from 'mongoose';

import Twitter from '../models/Twitter';

export const getTweets = (req: Request, res: Response): void => {
    Twitter.find((err, data) => {
        console.log(data);

        res.json(data);

        if (err) {
            console.log(err);
        }
    });
};

export const getTweet = async (req: Request, res: Response): Promise<any> => {
    const tweetId = req.params.tweetId;

    console.log('Tweet ID', tweetId);

    // This line of code fixes the CastError: Cast to ObjectId failed for value "favicon.ico" (type string) at path "_id" for model "contents"

    if (!mongoose.Types.ObjectId.isValid(tweetId)) return false;

    await Twitter.findById(tweetId).exec();

    Twitter.findById(tweetId, (err: any, tweet: any) => {
        console.log(tweet);

        res.json(tweet);

        if (err) {
            console.log(err);
        }
    });
};

export const postTweet = (req: Request, res: Response) => {
    const { tweet, img } = req.body;

    const twitter = new Twitter({ tweet: tweet, img: img });

    twitter.save();

    console.log('Tweet Created');

    res.status(201).json({ msg: 'Tweet Created' });
};

export const updateTweet = (req: Request, res: Response) => {
    const tweetId = req.params.tweetId;

    const { tweet, img } = req.body;

    Twitter.findByIdAndUpdate(tweetId, { tweet: tweet, img: img }).then(() => {
        console.log(`Tweet ${tweetId} Updated`);

        res.json({ msg: `Tweet ${tweetId} Updated` });
    });
};

export const deleteTweet = (req: Request, res: Response) => {
    const tweetId = req.body.tweetId;

    Twitter.findByIdAndRemove(tweetId, () => {
        res.json({ msg: `Tweet ${tweetId} Deleted` });
    });
};
Enter fullscreen mode Exit fullscreen mode

models/Twitter.ts

import { Schema, model } from 'mongoose';

interface Twitter {
    tweet: string;

    img: string;
}

const schema = new Schema<Twitter>({
    tweet: { type: String, required: true },

    img: { type: String, required: false },
});

const TwitterModel = model<Twitter>('contents', schema);

export default TwitterModel;
Enter fullscreen mode Exit fullscreen mode

routes/Admin.ts

import express from 'express';

import { getTweets, getTweet, postTweet, updateTweet, deleteTweet } from '../controllers/Admin';

const router = express.Router();

router.get('/', getTweets);

router.get('/:tweetId', getTweet);

router.post('/delete', deleteTweet);

router.post('/tweet', postTweet);

router.post('/:tweetId', updateTweet);

export default router;
Enter fullscreen mode Exit fullscreen mode

app.ts

import dotenv from 'dotenv';

dotenv.config();

console.log(process.env.DB_HOST);

import express from 'express';

import cors from 'cors';

import mongoose from 'mongoose';

import adminRoute from './routes/Admin';

const app = express();

app.use(cors());

app.use(express.urlencoded({ extended: false }));

app.use(express.json());

app.use('/', adminRoute);

const port = process.env.PORT || 8080;

mongoose

    // Use DB_HOST_DOCKER to connect to the MongoDB Database in the Docker Container

    .connect(`${process.env.DB_HOST_LOCAL}`)

    .then(() => {
        app.listen(port, () => console.log(`Server and database running on port ${port}, http://localhost:${port}`));
    })

    .catch((err: any) => {
        console.log(err);
    });
Enter fullscreen mode Exit fullscreen mode

.env

DB_HOST_LOCAL="mongodb://127.0.0.1:27017/twitter"

DB_HOST_DOCKER="mongodb://127.0.0.1:2717/twitter"
Enter fullscreen mode Exit fullscreen mode

该应用程序设置为连接到本地 MongoDB 数据库,但您可以在app.ts文件中更改它,并且可以在文件中找到数据库连接字符串.env

使用下面的命令启动服务器。

npm run dev
Enter fullscreen mode Exit fullscreen mode
测试 API

我将使用 Postman,但您可以使用任何您想要的 API 测试工具。如果您想查看它的完整运行情况,首先需要做的就是向数据库添加一些数据(如果您还没有这样做)。使用“创建推文”路由进行此操作,并查看以下屏幕截图中的示例。

获取所有推文

https://res.cloudinary.com/d74fh3kw/image/upload/c_scale,w_800/v1650806989/postman-get-tweets_osgha3.jpg

通过 ID 获取推文

https://res.cloudinary.com/d74fh3kw/image/upload/c_scale,w_800/v1650807143/postman-get-tweet_lc03qt.jpg

创建推文

https://res.cloudinary.com/d74fh3kw/image/upload/c_scale,w_800/v1650807209/postman-post-tweet_p7fycd.jpg

根据 ID 更新推文

https://res.cloudinary.com/d74fh3kw/image/upload/c_scale,w_800/v1650807250/postman-update-tweet_vvookc.jpg

删除推文

https://res.cloudinary.com/d74fh3kw/image/upload/c_scale,w_800/v1650807306/postman-delete-tweet_mp4bs1.jpg

后端 Express PostgreSQL

我们将使用https://typeorm.io/创建一个连接到 PostgreSQL 数据库的 Express.js 应用程序。

您应该位于complete-react-developer文件夹内

运行以下命令来构建您的项目。

mkdir backend-express-postgresql
cd backend-express-postgresql
tsc --init
npm init -y
npm i express @types/express @types/node ts-node cors @types/cors typescript rimraf copy-files dotenv nodemon pg reflect-metadata typeorm
mkdir src
cd src
touch app.ts app-data-source.ts
mkdir entity
cd entity
touch Tweet.ts
cd ../..
Enter fullscreen mode Exit fullscreen mode

在代码编辑器中打开项目,找到该tsconfig.json文件并用下面的代码替换其中的所有代码。

{
    "compilerOptions": {
        "lib": ["es5", "es6", "dom"],

        "target": "es5",

        "module": "commonjs",

        "moduleResolution": "node",

        "emitDecoratorMetadata": true,

        "experimentalDecorators": true,

        "rootDir": "./src",

        "outDir": "./dist/src"
    }
}
Enter fullscreen mode Exit fullscreen mode

下一步打开package.json文件并添加这些运行脚本。

"scripts": {

"start": "node dist/src/app.js",

"dev": "nodemon src/app.ts",

"clean": "rimraf dist/",

"build": "npm run clean && tsc && npm run copy-files",

"copy-files": "copyfiles -u 1 src/**/*.html src/**/*.css src/**/*.json dist/src"

},
Enter fullscreen mode Exit fullscreen mode

将下面的代码添加到相应的文件。

entity/Tweet.ts

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Tweet {
    @PrimaryGeneratedColumn('uuid')
    id: string;

    @Column()
    tweet: string;

    @Column()
    img: string;
}
Enter fullscreen mode Exit fullscreen mode

app-data-source.ts

import { DataSource } from 'typeorm';

export const myDataSource = new DataSource({
    type: 'postgres',

    host: 'localhost',

    port: 5432,

    username: 'postgres',

    password: '',

    database: 'twitter',

    entities: ['dist/src/entity/*.js'],

    logging: true,

    synchronize: true,
});
Enter fullscreen mode Exit fullscreen mode

app.ts

import * as express from 'express';

import { Request, Response } from 'express';

import { Tweet } from './entity/Tweet';

import { myDataSource } from './app-data-source';

// establish database connection

myDataSource

    .initialize()

    .then(() => {
        console.log('Data Source has been initialized!');
    })

    .catch((err) => {
        console.error('Error during Data Source initialization:', err);
    });

// create and setup express app

const app = express();

app.use(express.json());

app.use(express.urlencoded({ extended: false }));

// register CRUD routes

// CREATE

// READ

// UPDATE

// DELETE

// READ: All tweets

app.get('/tweets', async function (req: Request, res: Response) {
    const tweets = await myDataSource.getRepository(Tweet).find();

    res.json(tweets);
});

// READ: Tweet by ID

app.get('/tweets/:id', async function (req: Request, res: Response) {
    const results = await myDataSource.getRepository(Tweet).findOneBy({
        id: req.params.id,
    });

    return res.send(results);
});

// CREATE: New tweet

app.post('/tweets', async function (req: Request, res: Response) {
    const tweet = await myDataSource.getRepository(Tweet).create(req.body);

    const results = await myDataSource.getRepository(Tweet).save(tweet);

    return res.send(results);
});

// UPDATE: Tweet by ID

app.put('/tweets/:id', async function (req: Request, res: Response) {
    const tweet = await myDataSource.getRepository(Tweet).findOneBy({
        id: req.body.id,
    });

    myDataSource.getRepository(Tweet).merge(tweet, req.body);

    const results = await myDataSource.getRepository(Tweet).save(tweet);

    return res.send(results);
});

// DELETE: Tweet by ID

app.delete('/tweets/:id', async function (req: Request, res: Response) {
    const results = await myDataSource.getRepository(Tweet).delete(req.body.id);

    return res.send(results);
});

const port = process.env.PORT || 8080;

// start express server

app.listen(port, () => console.log(`Server and database running on port ${port}, http://localhost:${port}`));
Enter fullscreen mode Exit fullscreen mode

该应用程序已设置为连接到本地 PostgreSQL 数据库,但您可以在app-data-source.ts文件中更改此设置。如果需要,可以在 Docker 部分找到 Docker 连接设置。请记住,在连接 PostgreSQL 数据库之前,您需要先设置并运行它。

使用以下命令运行该应用程序。

EntityMetadataNotFoundError: No metadata for "Tweet" was found.警告:如果您尝试使用 nodemon 启动应用程序的命令,可能会出现错误npm run dev。我认为这与静态和动态数据以及 nodemon 自动重新加载有关。因此,更安全的做法是使用以下命令,并使用 Node 服务器进行干净构建,除非您手动重启它,否则它不会更新。

npm run build
npm run start
Enter fullscreen mode Exit fullscreen mode

您现在应该熟悉如何使用 REST API 工具,但是本例中的路由和 CRUD 请求略有不同。请参阅下面的示例,并不要忘记使用 CREATE tweet 路由将一些数据添加到数据库中,以便您可以看到一些数据。

获取所有推文

请求:GET
路由:http://localhost:8080/tweets

通过 ID 获取推文

请求:GET
路由:http://localhost:8080/tweets/d5d29839-788f-4d23-99ee-82b49ff1bbf1

创建推文

请求:POST
路由:http://localhost:8080/tweets
主体原始:{“tweet”:'Hello World',img:“”}

根据 ID 更新推文

请求:PUT
路由:http://localhost:8080/tweets/d5d29839-788f-4d23-99ee-82b49ff1bbf1
原始正文:{“tweet”:'Hello Moon',img:“”}

根据 ID 删除推文

请求:DELETE
路由:http://localhost:8080/tweets/d5d29839-788f-4d23-99ee-82b49ff1bbf1
正文:x-www-form-urlencoded
键:id
值:您的 id

Nest 应用

后端 Nest MongoDB

现在是时候创建用于连接 MongoDB 的 Nest 后端了。进入complete-react-developer文件夹并运行以下命令。选择您喜欢的包管理器(我这里选择的是 npm)。如果您选择了其他选项,请记住稍后运行正确的命令。

nest new backend-nest-mongodb
Enter fullscreen mode Exit fullscreen mode

在代码编辑器中打开项目,准备生成一些控制器和服务文件。我们还将首先在命令行中将 mongoose 安装cdbackend-nest-mongodb文件夹中,并运行以下命令。

cd backend-nest-mongodb
npm install --save @nestjs/mongoose mongoose
nest g controller twitter
nest g service twitter
Enter fullscreen mode Exit fullscreen mode

在创建其他项目文件之前,让我们先进行一些文件清理。删除以下文件:

app.service.ts
app.controller.ts
app.controller.spec.ts

现在是时候创建该项目的其余文件了。进入backend-nest-mongodb的根文件夹并运行以下命令。

touch src/twitter/twitter.module.ts
mkdir src/twitter/{dto,schemas}
touch src/twitter/dto/create-twitter.dto.ts
touch src/twitter/schemas/twitter.schema.ts
Enter fullscreen mode Exit fullscreen mode

我们已经创建了该项目所需的所有文件,现在让我们添加代码。使用以下代码添加或替换现有文件中的代码:

app.module.ts

import { Module } from '@nestjs/common';

import { MongooseModule } from '@nestjs/mongoose';

import { TwitterController } from './twitter/twitter.controller';

import { TwitterService } from './twitter/twitter.service';

import { TwitterModule } from './twitter/twitter.module';

@Module({
    imports: [
        TwitterModule,

        // Local MongoDb database

        // Change the port to 127.0.0.1:2717 to connect to Docker

        MongooseModule.forRoot('mongodb://127.0.0.1:27017/twitter'),
    ],

    controllers: [TwitterController],

    providers: [TwitterService],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

twitter.service.ts

import { Model } from 'mongoose';

import { Injectable } from '@nestjs/common';

import { InjectModel } from '@nestjs/mongoose';

import { Twitter, TwitterDocument } from './schemas/twitter.schema';

import { CreateTwitterDto } from './dto/create-twitter.dto';

@Injectable()
export class TwitterService {
    constructor(@InjectModel(Twitter.name) private twitterModel: Model<TwitterDocument>) {}

    async create(createTwitterDto: CreateTwitterDto): Promise<Twitter> {
        const createdTwitter = new this.twitterModel(createTwitterDto);

        return createdTwitter.save();
    }

    async findAll(): Promise<Twitter[]> {
        return this.twitterModel.find().exec();
    }

    async findOne(id: string): Promise<Twitter> {
        return this.twitterModel.findOne({ _id: id });
    }

    async update(id: string, twitter: Twitter): Promise<Twitter> {
        return this.twitterModel.findByIdAndUpdate(id, twitter, { new: true });
    }

    async delete(id: string): Promise<Twitter> {
        return this.twitterModel.findByIdAndRemove({ _id: id });
    }
}
Enter fullscreen mode Exit fullscreen mode

twitter.module.ts

import { Module } from '@nestjs/common';

import { MongooseModule } from '@nestjs/mongoose';

import { Twitter, TwitterSchema } from './schemas/twitter.schema';

@Module({
    imports: [MongooseModule.forFeature([{ name: Twitter.name, schema: TwitterSchema }])],

    exports: [MongooseModule],
})
export class TwitterModule {}
Enter fullscreen mode Exit fullscreen mode

twitter.controller.ts

import { Controller, Get, Post, Body, Param, Put, Delete } from '@nestjs/common';

import { CreateTwitterDto, TwitterDto } from './dto/create-twitter.dto';

import { TwitterService } from './twitter.service';

@Controller('tweets')
export class TwitterController {
    constructor(private twitterService: TwitterService) {}

    @Post()
    async create(@Body() createTwitterDto: CreateTwitterDto) {
        this.twitterService.create(createTwitterDto);
    }

    @Get()
    async findAll(): Promise<TwitterDto[]> {
        return this.twitterService.findAll();
    }

    @Get(':id')
    async findOne(@Param('id') id): Promise<TwitterDto> {
        return this.twitterService.findOne(id);
    }

    @Put(':id')
    update(
        @Body() updateTwitterDto: CreateTwitterDto,

        @Param('id') id
    ): Promise<TwitterDto> {
        return this.twitterService.update(id, updateTwitterDto);
    }

    @Delete(':id')
    delete(@Param('id') id): Promise<TwitterDto> {
        return this.twitterService.delete(id);
    }
}
Enter fullscreen mode Exit fullscreen mode

schemas/twitter.schema.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';

import { Document } from 'mongoose';

export type TwitterDocument = Twitter & Document;

@Schema()
export class Twitter {
    @Prop()
    tweet: string;

    @Prop()
    img: string;
}

export const TwitterSchema = SchemaFactory.createForClass(Twitter);
Enter fullscreen mode Exit fullscreen mode

dto/create-twitter.dto.ts

export class CreateTwitterDto {
    id?: string;

    tweet: string;

    img: string;
}

export class TwitterDto {
    id?: string;

    tweet: string;

    img: string;
}
Enter fullscreen mode Exit fullscreen mode

现在一切都应该设置好了,后端已配置为连接到本地 MongoDB 数据库。您可以通过编辑app.module.ts文件中的连接字符串将其更改为 Docker。

运行以下命令以在监视模式下启动应用程序。

npm run start:dev
Enter fullscreen mode Exit fullscreen mode

需要特别指出的是,NestJS 应用程序默认运行在 3000 端口,这与我们的 React 应用程序将要使用的默认端口相同。因此,为了保持一致性,您可能需要将其更改为 8080 或其他端口。您可以在main.ts文件中执行此操作。此外,您还需要启用 CORS,否则在尝试将后端连接到前端时会收到恼人的 CORS 错误。

main.ts

import { NestFactory } from '@nestjs/core';

import { AppModule } from './app.module';

async function bootstrap() {
    const app = await NestFactory.create(AppModule);

    app.enableCors();

    await app.listen(8080);
}

bootstrap();
Enter fullscreen mode Exit fullscreen mode

路线应该与以前相同,这里是您可以在 Postman 或您正在使用的任何 REST API 工具中进行测试的复习:

获取所有推文

请求:GET
路由:http://localhost:8080/tweets

通过 ID 获取推文

请求:GET
路由:http://localhost:8080/tweets/d5d29839-788f-4d23-99ee-82b49ff1bbf1

创建推文

请求:POST
路由:http://localhost:8080/tweets
主体原始:{“tweet”:'Hello World',img:“”}

根据 ID 更新推文

请求:PUT
路由:http://localhost:8080/tweets/d5d29839-788f-4d23-99ee-82b49ff1bbf1
原始正文:{“tweet”:'Hello Moon',img:“”}

根据 ID 删除推文

请求:删除
路由:http://localhost:8080/tweets/d5d29839-788f-4d23-99ee-82b49ff1bbf1

后端 Nest PostgreSQL

最后,我们将创建连接到 PostgreSQL 的 Nest 后端。此阶段结束后,我们将最终转向 React 前端。请确保您位于complete-react-developer文件夹中,并运行以下命令。与上一章一样,选择您喜欢的包管理器,我将选择 npm。如果您选择其他选项,请记住稍后运行正确的命令。

nest new backend-nest-postgresql
Enter fullscreen mode Exit fullscreen mode

在代码编辑器中打开项目,准备生成一些控制器和服务文件。我们还将安装 PostgreSQL 和TypeORM,以便连接到 PostgreSQL 数据库。首先,在命令行中cd进入backend-nest-postgresql文件夹,并运行以下命令。

cd backend-nest-postgresql
npm install --save pg @nestjs/typeorm typeorm
nest g controller twitter
nest g service twitter
Enter fullscreen mode Exit fullscreen mode

在创建其他项目文件之前,让我们先进行一些文件清理。删除以下文件:

app.service.ts
app.controller.ts
app.controller.spec.ts

现在是时候创建该项目的其余文件了。当您进入backend-nest-postgresql的根文件夹时,运行以下命令。

touch src/twitter/{twitter.module.ts,twitter.entity.ts}
mkdir src/twitter/dto
touch src/twitter/dto/twitter.dto.ts
Enter fullscreen mode Exit fullscreen mode

我们已经创建了该项目所需的所有文件,现在让我们添加代码。使用以下代码添加或替换现有文件中的代码:

app.module.ts

import { Module } from '@nestjs/common';

import { TypeOrmModule } from '@nestjs/typeorm';

import { TwitterController } from './twitter/twitter.controller';

import { TwitterService } from './twitter/twitter.service';

import { TwitterModule } from './twitter/twitter.module';

import { Connection } from 'typeorm';

@Module({
    imports: [
        TypeOrmModule.forRoot({
            type: 'postgres',

            host: 'localhost',

            port: 5432,

            username: 'postgres',

            password: '',

            database: 'twitter',

            entities: ['dist/**/*.entity{.ts,.js}'],

            synchronize: false,
        }),

        TwitterModule,
    ],

    controllers: [TwitterController],

    providers: [TwitterService],
})
export class AppModule {
    constructor(private connection: Connection) {}
}
Enter fullscreen mode Exit fullscreen mode

twitter.service.ts

import { Injectable, NotFoundException } from '@nestjs/common';

import { InjectRepository } from '@nestjs/typeorm';

import { DeleteResult, InsertResult, Repository } from 'typeorm';

import { Twitter } from './twitter.entity';

@Injectable()
export class TwitterService {
    constructor(
        @InjectRepository(Twitter)
        private twitterRepository: Repository<Twitter>
    ) {}

    async addTwitter(twitter: Twitter): Promise<InsertResult> {
        return this.twitterRepository.insert(twitter);
    }

    async findAll(): Promise<Twitter[]> {
        return this.twitterRepository.find();
    }

    async findOne(id: string): Promise<Twitter> {
        return this.twitterRepository.findOne(id);
    }

    async update(id: string, twitter: Twitter): Promise<Twitter> {
        const twitterUpdate = await this.findOne(id);

        if (twitterUpdate === undefined) {
            throw new NotFoundException();
        }

        await this.twitterRepository.update(id, twitter);

        return this.twitterRepository.findOne(id);
    }

    async delete(id: string): Promise<DeleteResult> {
        const twitterUpdate = await this.findOne(id);

        if (twitterUpdate === undefined) {
            throw new NotFoundException();
        }

        return this.twitterRepository.delete(id);
    }
}
Enter fullscreen mode Exit fullscreen mode

twitter.module.ts

import { Module } from '@nestjs/common';

import { TypeOrmModule } from '@nestjs/typeorm';

import { TwitterController } from './twitter.controller';

import { TwitterService } from './twitter.service';

import { Twitter } from './twitter.entity';

@Module({
    imports: [TypeOrmModule.forFeature([Twitter])],

    controllers: [TwitterController],

    providers: [TwitterService],

    exports: [TypeOrmModule],
})
export class TwitterModule {}
Enter fullscreen mode Exit fullscreen mode

twitter.entity.ts

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Twitter {
    @PrimaryGeneratedColumn('uuid')
    id: string;

    @Column()
    tweet: string;

    @Column()
    img: string;
}
Enter fullscreen mode Exit fullscreen mode

twitter.controller.ts

import { Controller, Get, Post, Patch, Delete, Param, Body } from '@nestjs/common';

import { TwitterService } from './twitter.service';

import { TwitterDto } from './dto/twitter.dto';

import { Twitter } from './twitter.entity';

@Controller('tweets')
export class TwitterController {
    constructor(private twitterService: TwitterService) {}

    @Post()
    create(@Body() twitter: Twitter) {
        return this.twitterService.addTwitter(twitter);
    }

    @Get()
    findAll(): Promise<TwitterDto[]> {
        return this.twitterService.findAll();
    }

    @Get(':id')
    getOneTwitter(@Param('id') id: string): Promise<Twitter> {
        return this.twitterService.findOne(id);
    }

    @Patch(':id')
    updateTwitter(
        @Param('id') id: string,

        @Body() twitter: Twitter
    ): Promise<Twitter> {
        return this.twitterService.update(id, twitter);
    }

    @Delete(':id')
    deleteTwitter(@Param('id') id: string) {
        return this.twitterService.delete(id);
    }
}
Enter fullscreen mode Exit fullscreen mode

dto/twitter.dto.ts

export class TwitterDto {
    tweet: string;

    img: string;
}
Enter fullscreen mode Exit fullscreen mode

现在一切都应该设置好了,后端已配置为连接到本地 PostgreSQL 数据库。您可以通过编辑app.module.ts文件内的连接详细信息将其更改为 Docker。

虽然此应用程序使用了一个名为twitter的数据库表,但有一点需要注意。请查看示例 SQL,您可以使用它来生成一些快速测试数据。如果出现错误,可能是因为它期望找到一个名为twitter的表。

CREATE TABLE twitter (



id UUID DEFAULT gen_random_uuid (),



tweet VARCHAR(280) NOT NULL,



img VARCHAR(500) NOT NULL



);



INSERT INTO twitter (tweet, img)



VALUES ('Hello World!', ''), ('Content creation and programming are basically full time jobs. I have enough projects and work to keep me busy for years. Working in tech is definitely going to entertain you for a long time which is why so many people want to transition into this field.', ''), ('JavaScript developers are forever in high demand', '');
Enter fullscreen mode Exit fullscreen mode

运行以下命令以在监视模式下启动应用程序。与之前类似,路由也有一些区别。另外,别忘了在main.ts文件中将端口更改为 8080。与之前一样,您需要启用 CORS,否则在尝试将后端连接到前端时会收到恼人的 CORS 错误。

main.ts

import { NestFactory } from '@nestjs/core';

import { AppModule } from './app.module';

async function bootstrap() {
    const app = await NestFactory.create(AppModule);

    app.enableCors();

    await app.listen(8080);
}

bootstrap();
Enter fullscreen mode Exit fullscreen mode
npm run start:dev
Enter fullscreen mode Exit fullscreen mode

您可以在 Postman 或您正在使用的任何 REST API 工具中测试:

获取所有推文

请求:GET
路由:http://localhost:8080/tweets

通过 ID 获取推文

请求:GET
路由:http://localhost:8080/tweets/d5d29839-788f-4d23-99ee-82b49ff1bbf1

创建推文

请求:POST
路由:http://localhost:8080/tweets
主体原始:{“tweet”:'Hello World',img:“”}

根据 ID 更新推文

请求:PATCH
路由:http://localhost:8080/tweets/d5d29839-788f-4d23-99ee-82b49ff1bbf1
原始正文:{“tweet”:'Hello Moon',img:“”}

根据 ID 删除推文

请求:DELETE
路由:http://localhost:8080/tweets/d5d29839-788f-4d23-99ee-82b49ff1bbf1
正文:x-www-form-urlencoded
键:id
值:您的 id

设置前端

终于到了前端部分!它不会像我们刚刚完成的后端部分那么长,因为只有一个 React 前端!

构建 Twitter 克隆应用程序

该应用程序将是一个非常简单的 Twitter 克隆版。你可以创建、阅读和删除推文。目前没有更新/编辑推文的选项,😂 不过,更新的端点已经存在于后端,所以如果你想的话,可以实现它。顺便说一句,这不是 Twitter 克隆课程,所以不要指望它像素完美,准确率也不要达到 100% 😁

代码库相当庞大,所以我不用复制粘贴代码十几次,也不用经历漫长的项目设置过程,而是直接创建应用程序并将其放在 GitHub 上。所以,你只需要克隆/下载代码库,然后运行安装脚本即可。

https://github.com/andrewbaisden/complete-react-developer

接下来,在代码编辑器中打开项目查看代码库,并在其各自的根文件夹中使用以下命令。设置说明也在 README 文件中。

设置

在您的计算机上启动 Docker 桌面应用程序

cd进入backend-nest-mongodbfrontend的根文件夹,然后运行以下命令安装依赖项。在这种情况下,当你尝试安装前端 React 应用程序的依赖项时,可能需要强制安装,否则可能会出现错误。

# Run this command inside of the backend-nest-mongodb folder
npm install

# Run this command inside of the frontend folder
npm install --force
Enter fullscreen mode Exit fullscreen mode

cd进入docker-twitter-mongodb的根文件夹并运行以下命令在 Docker 容器内启动 MongoDB 数据库。

docker compose up
Enter fullscreen mode Exit fullscreen mode

cd进入backend-nest-mongodb的根文件夹并运行以下命令来启动后端 NestJS 服务器。

npm run start:dev
Enter fullscreen mode Exit fullscreen mode

cd进入前端的根文件夹并运行以下命令来启动前端 React 服务器。

npm run start
Enter fullscreen mode Exit fullscreen mode

如果您想在 REST API 工具中测试它们,请使用Backend Nest MongoDB部分中的路由。

Twitter克隆应用程序

您应该看到您的数据库在 Docker 容器中运行,并且您的 Twitter Clone React 应用程序在浏览器中打开。

在前端根文件夹(即 React 所在的文件夹)中运行这些命令。以下命令将启动 Storybook。

# Starts Storybook
npm run storybook
Enter fullscreen mode Exit fullscreen mode

您应该会在浏览器中看到一个 Storybook 组件库,其中包含一个用于撰写推文的组件。您可以尝试修改控件中的名称,以查看它在演示中的外观。以下命令将运行单元测试和集成测试。

# Runs the React testing library unit and integration tests
npm run test
Enter fullscreen mode Exit fullscreen mode

您可能需要按aEnter 键来触发新的测试运行。所有测试都应该在您的控制台中通过。以下命令将启动 Cypress。

# Runs the Cypress End-To-End tests
npx cypress open
Enter fullscreen mode Exit fullscreen mode

一个新的 Cypress 窗口应该会打开。运行集成测试,准备好惊喜吧!它会自动为你发布 3 条推文!用你的 React 应用程序刷新网页,你也会在那里看到新的推文!

上下文 API

此应用程序使用 Context API 来处理全局状态。如果您希望应用程序连接到您的 MongoDB、PostgreSQL 或 Docker 数据库,则需要更改 API 路由和端口号http://localhost:8080/tweets。方法也一样,别忘了有些方法会使用 POST、PUT、PATCH、DELETE 等……这取决于您使用的后端。

src/contexts/TwitterContext.tsx

import { useEffect, useState, createContext, useContext } from 'react';

interface ContextProps {
    data: any;

    loading: boolean;

    handleToggleComposetweet: any;

    toggleComposeTweet: boolean;

    tweet: string;

    setTweet: any;

    postTweet: any;

    deleteTweet: any;
}

const TwitterContext = createContext({} as ContextProps);

export const useTwitter = () => useContext(TwitterContext);

const TwitterContextProvider = (props: any): any => {
    useEffect(() => {
        const getTweets = () => {
            const API = 'http://localhost:8080/tweets';

            fetch(API)
                .then((response) => {
                    console.log(response);

                    return response.json();
                })

                .then((data) => {
                    console.log(data);

                    setLoading(false);

                    setData(data);
                })

                .catch((err) => {
                    console.log(err);
                });
        };

        getTweets();
    }, []);

    const [data, setData] = useState([]);

    const [loading, setLoading] = useState(true);

    const [toggleComposeTweet, setToggleComposeTweet] = useState(false);

    const [tweet, setTweet] = useState('');

    const handleToggleComposetweet = () => {
        toggleComposeTweet === true ? setToggleComposeTweet(false) : setToggleComposeTweet(true);
    };

    const postTweet = () => {
        if (tweet === '') {
            let myHeaders = new Headers();

            myHeaders.append('Content-Type', 'application/json');

            let raw = JSON.stringify({
                tweet: 'Congratulations this is what happens when you post an empty tweet 🤪 Create some validation 🙃',

                img: '',
            });

            fetch('http://localhost:8080/tweets', { method: 'POST', headers: myHeaders, body: raw, redirect: 'follow' })
                .then((response) => response.text())

                .then((result) => console.log(result))

                .catch((error) => console.log('error', error));
        } else {
            let myHeaders = new Headers();

            myHeaders.append('Content-Type', 'application/json');

            let raw = JSON.stringify({
                tweet: tweet,

                img: '',
            });

            fetch('http://localhost:8080/tweets', { method: 'POST', headers: myHeaders, body: raw, redirect: 'follow' })
                .then((response) => response.text())

                .then((result) => console.log(result))

                .catch((error) => console.log('error', error));
        }
    };

    const deleteTweet = (tweetId: string) => {
        console.log('Deleted', tweetId);

        let urlencoded = new URLSearchParams();

        fetch(`http://localhost:8080/tweets/${tweetId}`, {
            method: 'DELETE',

            body: urlencoded,

            redirect: 'follow',
        })
            .then((response) => response.text())

            .then((result) => console.log(result))

            .catch((error) => console.log('error', error));

        window.location.reload();
    };

    const value = {
        data,

        loading,

        toggleComposeTweet,

        handleToggleComposetweet,

        postTweet,

        tweet,

        setTweet,

        deleteTweet,
    };

    return <TwitterContext.Provider value={value}>{props.children}</TwitterContext.Provider>;
};

export default TwitterContextProvider;
Enter fullscreen mode Exit fullscreen mode

使用 React Testing Library 和 Jest 进行测试

有两个测试文件,一个用于App.test.tsx,一个用于TwitterMenu.test.tsx

我将展示 的示例App.test.tsx。这些测试仅用于测试所需的文本是否显示在页面上。每个组件都应该有一个与之配套的测试文件。

App.test.tsx

import { render, screen } from '@testing-library/react';

import App from './App';

describe('<App />', () => {
    it('has a following text label', () => {
        render(<App />);

        const el = screen.getByText(/Following/i);

        expect(el).toBeTruthy();
    });

    it('has a followers text label', () => {
        render(<App />);

        const el = screen.getByText(/Followers/i);

        expect(el).toBeTruthy();
    });

    it('has a you might like heading', () => {
        render(<App />);

        const el = screen.getByText(/You might like/i);

        expect(el.innerHTML).toBe('You might like');
    });

    it('has a whats happening heading', () => {
        render(<App />);

        const el = screen.getByText(/Whats happening/i);

        expect(el.innerHTML).toBe('Whats happening');
    });
});
Enter fullscreen mode Exit fullscreen mode

使用 Cypress 进行端到端测试

本次 Cypress 测试将自动发布 3 条推文。所有操作均实时完成,推文将显示在您的数据库和实际应用中。

cypress/integratioin/tweet.spec.js

describe('user form flow', () => {
    beforeEach(() => {
        cy.viewport(1600, 900);

        cy.visit('http://localhost:3000/');
    });

    it('user posts a tweet', () => {
        // Post a tweet

        cy.get('.compose-tweet-btn').click();

        cy.get('textarea[name="tweet"]').type(
            'What happened to all that fun you were having?! Come on, lets try to enjoy this!'
        );

        cy.wait(3000);

        cy.get('.post-tweet-btn').click();
    });

    it('user posts a second tweet', () => {
        // Post a tweet

        cy.get('.compose-tweet-btn').click();

        cy.get('textarea[name="tweet"]').type('That was an Attack on Titan easter egg 🥚 😄');

        cy.wait(3000);

        cy.get('.post-tweet-btn').click();
    });

    it('user posts a third tweet', () => {
        // Post a tweet

        cy.get('.compose-tweet-btn').click();

        cy.get('textarea[name="tweet"]').type(
            'The Rumbling arrives on Marley 😱 https://www.youtube.com/watch?v=wT2H68kEmi8'
        );

        cy.wait(3000);

        cy.get('.post-tweet-btn').click();
    });
});
Enter fullscreen mode Exit fullscreen mode

部署

完成应用程序构建后,最后一步就是部署。您需要将应用程序上线,以便每个人都能看到它。市面上有很多平台,但以下是我推荐的 5 个最佳平台(排名不分先后)。

  1. Netlify
  2. 韦尔塞尔
  3. 赫罗库
  4. DigitalOcean
  5. AWS

最后的想法

我们涵盖了 MERN 的所有内容,包括 TypeScript、SQL、测试驱动开发、端到端测试,甚至 Docker!恭喜你,你的技能和工作前景都得到了提升,变得超级棒🔥🚀

尝试使用数据库和 React 前端,你可以用它做很多事情。例如,创建更多 Storybook 组件、进行集成测试、添加编辑推文的功能,以及让图片和视频等媒体显示在推文中。

文章来源:https://dev.to/andrewbaisden/the-complete-modern-react-developer-2022-3257
PREV
NodeJS 18 Fetch API
NEXT
我的终极工具包 - 10 个提升生产力的 AI 工具