2024年前端最新React+Ts+Node+MongoDB 构建 Todo App项目总结(3),写给1-3年前端程序员的几点建议

最后

正值招聘旺季,很多小伙伴都询问我有没有前端方面的面试题!

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

前端资料图.PNG

res

.status(201)

.json({ message: “Todo added”, todo: newTodo, todos: allTodos })

} catch (error) {

throw error

}

}

addTodo()  函数接收包含用户输入数据的 body 对象。

接下来,我使用类型转换来避免拼写错误,并限制  body  变量与  ITodo  类型匹配,然后基于该模块创建一个新的 Todo。

有了这些,我们现在可以在 DB 中保存 Todo 并返回新增的 Todo 和更新后的 todos 数组。

  • controllers/todos/index.ts

const updateTodo = async (req: Request, res: Response): Promise => {

try {

const {

params: { id },

body,

} = req

const updateTodo: ITodo | null = await Todo.findByIdAndUpdate(

{ _id: id },

body

)

const allTodos: ITodo[] = await Todo.find()

res.status(200).json({

message: “Todo updated”,

todo: updateTodo,

todos: allTodos,

})

} catch (error) {

throw error

}

}

为了实现更新 todo, 我们需要拿到 id 和从  req  对象中获取 body,然后把他们传入  findByIdAndUpdate(),这个函数将会在数据库中找到 Todo 并且更新它。

  • controllers/todos/index.ts

const deleteTodo = async (req: Request, res: Response): Promise => {

try {

const deletedTodo: ITodo | null = await Todo.findByIdAndRemove(

req.params.id

)

const allTodos: ITodo[] = await Todo.find()

res.status(200).json({

message: “Todo deleted”,

todo: deletedTodo,

todos: allTodos,

})

} catch (error) {

throw error

}

}

export { getTodos, addTodo, updateTodo, deleteTodo }

deleteTodo()  函数允许你从数据库中删除 Todo。在这里,我们从 req 中拿到 id,并把它作为参数传递给  findByIdAndRemove(),来获取到对应的 Todo 并从 DB 中删除它。

接下来,导出这些函数以便我们在其他文件中使用它们。也就是说,我们现在可以为 API 创建一些路由,并使用这些方法来处理请求。

创建 API 路由

  • routes/index.ts

import { Router } from “express”

import { getTodos, addTodo, updateTodo, deleteTodo } from “…/controllers/todos”

const router: Router = Router()

router.get(“/todos”, getTodos)

router.post(“/add-todo”, addTodo)

router.put(“/edit-todo/:id”, updateTodo)

router.delete(“/delete-todo/:id”, deleteTodo)

export default router

我们创建四个路由对应从数据库中获取、新增、更新和删除 todo。因为我们已经创建了函数,所以唯一要做的就是导入这些方法并将它们作为参数传递。

到目前为止,我们已经谈了很多,但是仍然没有启动服务器。所以,我们在下一节中解决这个问题。

创建服务器

在创建服务器之前,我们需要在  nodemon.json  加一些环境变量来保存 MongoDB 的凭据。

  • nodemon.json

{

“env”: {

“MONGO_USER”: “your-username”,

“MONGO_PASSWORD”: “your-password”,

“MONGO_DB”: “your-db-name”

}

}

你可以在  MongoDB Atlas,通过创一个新集群来得到凭据。

  • app.ts

import express, { Express } from “express”

import mongoose from “mongoose”

import cors from “cors”

import todoRoutes from “./routes”

const app: Express = express()

const PORT: string | number = process.env.PORT || 4000

app.use(cors())

app.use(todoRoutes)

const uri: string = mongodb+srv://${process.env.MONGO_USER}:${process.env.MONGO_PASSWORD}@clustertodo.raz9g.mongodb.net/${process.env.MONGO_DB}?retryWrites=true&w=majority

const options = { useNewUrlParser: true, useUnifiedTopology: true }

mongoose.set(“useFindAndModify”, false)

mongoose

.connect(uri, options)

.then(() =>

app.listen(PORT, () =>

console.log(Server running on http://localhost:${PORT})

)

)

.catch(error => {

throw error

})

这里,我们首先从导入  express  库开始,这使用我们能调用  use()  方法,这个方法将帮助处理 Todo 路由。

然后,我们用  mongoose  包,通过读取  nodemon.json  带凭证的 url 去连接 MongoDB。

就是说,现在如果我们能成功连接 MongoDB,服务器就会启动,否则,会抛出错误。

我们现在已经通过 Node、Express、TypeScript 和 MongoDB 完成 api 的构建。现在我们开始用 React 和 TypeScript 构建客户端。

用 React 和 TypeScript 创建客户端


构建

为了创建一个新的 React 应用,我将会使用 create-react-app ——你可以用其他你想用的方法。

所以,在终端运行以下代码:

npx create-react-app my-app --template typescript

然后,为了能获取远程数据安装 Axios 库。

yarn add axios

安装完成后,按照以下目录构建项目:

├── node_modules

├── public

├── src

|  ├── API.ts

|  ├── App.test.tsx

|  ├── App.tsx

|  ├── components

|  |  ├── AddTodo.tsx

|  |  └── TodoItem.tsx

|  ├── index.css

|  ├── index.tsx

|  ├── react-app-env.d.ts

|  ├── setupTests.ts

|  └── type.d.ts

├── tsconfig.json

├── package.json

└── yarn.lock

这样,我们有一个相对简单的文件结构。最值得注意的是  src/type.d.ts  被用来存放类型。我几乎在每个文件中都使用了它们,所以我添加了扩展  .d.ts  ,使类型全局可用。现在我们不再需要导入它们。

创建 Todo 类型

  • src/type.d.ts

interface ITodo {

_id: string

name: string

description: string

status: boolean

createdAt?: string

updatedAt?: string

}

interface TodoProps {

todo: ITodo

}

type ApiDataType = {

message: string

status: string

todos: ITodo[]

todo?: ITodo

}

这里,  ITodo  接口需要跟 API 返回的数据类型一样。这里没有  mongoose  , 所以需要加一些额外的属性来匹配 API 定义的数据类型。

然后,我们用相同的的接口定义  TodoProps  ,组件会接受它并渲染数据。

现在我们已经定义了类型——现在让我们开始从 API 获取数据。

从API获取数据

  • src/API.ts

import axios, { AxiosResponse } from “axios”

const baseUrl: string = “http://localhost:4000”

export const getTodos = async (): Promise<AxiosResponse> => {

try {

const todos: AxiosResponse = await axios.get(

baseUrl + “/todos”

)

return todos

} catch (error) {

throw new Error(error)

}

}

我们需要导入  axios,通过 api 来请求数据,然后,用  getTodos()  函数从服务端获取数据。它将返回  AxiosResponse  为类型的 promise, 保存获取到的  ApiDataType  类型的 Todos。

  • src/API.ts

export const addTodo = async (

formData: ITodo

): Promise<AxiosResponse> => {

try {

const todo: Omit<ITodo, “_id”> = {

name: formData.name,

description: formData.description,

status: false,

}

const saveTodo: AxiosResponse = await axios.post(

baseUrl + “/add-todo”,

todo

)

return saveTodo

} catch (error) {

throw new Error(error)

}

}

这个函数接受用户输入的数据作为参数并返回 promise。这里,我们需要去掉  _id  属性因为 MongoDB 会自动生成。

  • src/API.ts

export const updateTodo = async (

todo: ITodo

): Promise<AxiosResponse> => {

try {

const todoUpdate: Pick<ITodo, “status”> = {

status: true,

}

const updatedTodo: AxiosResponse = await axios.put(

${baseUrl}/edit-todo/${todo._id},

todoUpdate

)

return updatedTodo

} catch (error) {

throw new Error(error)

}

}

为了实现更新 Todo,我们必须传入更新后的数据和对象 id。这里,我们需要更改 Todo 的  状态  ,那么在发送到服务器之前我们只需要选择所需的属性即可。

  • src/API.ts

export const deleteTodo = async (

_id: string

): Promise<AxiosResponse> => {

try {

const deletedTodo: AxiosResponse = await axios.delete(

${baseUrl}/delete-todo/${_id}

)

return deletedTodo

} catch (error) {

throw new Error(error)

}

}

这里,我们也有一个函数接受  _id  属性作为参数并返回 promise。

有了这些,我们现在可以转到 components 文件夹并向其文件中添加一些有意义的代码。

创建组件

增加 Todo 表单
  • components/AddTodo.tsx

import React from “react”

type Props = TodoProps & {

updateTodo: (todo: ITodo) => void

deleteTodo: (_id: string) => void

}

const Todo: React.FC = ({ todo, updateTodo, deleteTodo }) => {

const checkTodo: string = todo.status ? line-through : “”

return (

{todo.name}

{todo.description}

<button

onClick={() => updateTodo(todo)}

className={todo.status ? hide-button : “Card–button__done”}

Complete

<button

onClick={() => deleteTodo(todo._id)}

className=“Card–button__delete”

Delete

)

}

export default Todo

这里有一个 React 类型的函数组件。FC (FC 代表函数组件),它接收  saveTodo()  方法为 props,该方法允许我们将数据保存到数据库。

然后,我们创建  formData  state,它需要匹配 ITodo 类型来满足编译器的要求。这就是我们将它传递给 useState hook 的原因。我们还需要添加一个替代类型({}),因为初始状态是个空对象。

有了这些,我们现在可以继续下一步,展示获取的数据。

展示 Todo

  • components/TodoItem.tsx

import React from “react”

type Props = TodoProps & {

updateTodo: (todo: ITodo) => void

deleteTodo: (_id: string) => void

}

const Todo: React.FC = ({ todo, updateTodo, deleteTodo }) => {

const checkTodo: string = todo.status ? line-through : “”

return (

{todo.name}

{todo.description}

<button

onClick={() => updateTodo(todo)}

className={todo.status ? hide-button : “Card–button__done”}

Complete

<button

onClick={() => deleteTodo(todo._id)}

className=“Card–button__delete”

Delete

)

}

export default Todo

这里,我们需要继承  TodoProps  类型并加入  updateTodo  和  deleteTodo  函数,作为 props 传递给组件。

现在,当传入 Todo 对象,我们将能够显示它并更新或删除 Todo。

太棒了!现在我们可以到  App.tsx  文件并把最后一块拼图放进去。

获取和展示数据

  • App.tsx

import React, { useEffect, useState } from ‘react’

import TodoItem from ‘./components/TodoItem’

import AddTodo from ‘./components/AddTodo’

import { getTodos, addTodo, updateTodo, deleteTodo } from ‘./API’

const App: React.FC = () => {

const [todos, setTodos] = useState<ITodo[]>([])

useEffect(() => {

fetchTodos()

}, [])

const fetchTodos = (): void => {

getTodos()

.then(({ data: { todos } }: ITodo[] | any) => setTodos(todos))

.catch((err: Error) => console.log(err))

}

这里,我们首先导入组件和  API.ts  导出的函数。然后,我们传递  ITodo  类型的数组给  useState  并且把它初始化为空数组。

getTodos()  方法会返回 promise —— 因此,我们可以调用 then 函数并用获取到的数据更新 state,或者在发生任何错误时抛出一个错误。

有了这些,我们现在可以在组件组件成功挂载之后,调用  fetchTodos()  函数。

  • App.tsx

const handleSaveTodo = (e: React.FormEvent, formData: ITodo): void => {

e.preventDefault()

addTodo(formData)

.then(({ status, data }) => {

if (status !== 201) {

throw new Error(“Error! Todo not saved”)

}

setTodos(data.todos)

})

.catch(err => console.log(err))

}

当发送表单时,我们用  addTodo()  向服务端发送请求。如果 Todo 被成功保存,我们将更新数据,否则将会抛出错误。

  • App.tsx

const handleUpdateTodo = (todo: ITodo): void => {

updateTodo(todo)

.then(({ status, data }) => {

if (status !== 200) {

throw new Error(“Error! Todo not updated”)

}

setTodos(data.todos)

})

.catch(err => console.log(err))

数据结构与算法

这一块在笔试、面试的代码题中考核较多,其中常考的数据结构主要有:数组、链表、队列、栈、Set、Map、哈希表等,不同数据结构有不同的方法以及储存原理,这些算是技术岗的必备知识。算法部分主要分为两大块,排序算法与一些其他算法题

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

排序算法根据考频高低主要有:快速排序、归并排序、堆排序、冒泡排序、插入排序、选择排序、希尔排序、桶排序、基数排序、Timsort这十种,这类考核点要么是算法的时间、空间复杂度、稳定度,要么是直接手写代码,故在理解算法原理的同时,对JS语言版的排序算法代码也要加强记忆。

  • 二叉树层序遍历
  • B 树的特性,B 树和 B+树的区别
  • 尾递归
  • 如何写一个大数阶乘?递归的方法会出现什么问题?
  • 把多维数组变成一维数组的方法
  • 知道的排序算法 说一下冒泡快排的原理
  • Heap 排序方法的原理?复杂度?
  • 几种常见的排序算法,手写
  • 数组的去重,尽可能写出多个方法
  • 如果有一个大的数组,都是整型,怎么找出最大的前 10 个数
  • 知道数据结构里面的常见的数据结构
  • 找出数组中第 k 大的数组出现多少次,比如数组【1,2, 4,4,3,5】第二大的数字是 4,出现两次,所以返回 2
  • 合并两个有序数组
  • 给一个数,去一个已经排好序的数组中寻找这个数的位 置(通过快速查找,二分查找)

更新数据,否则将会抛出错误。

  • App.tsx

const handleUpdateTodo = (todo: ITodo): void => {

updateTodo(todo)

.then(({ status, data }) => {

if (status !== 200) {

throw new Error(“Error! Todo not updated”)

}

setTodos(data.todos)

})

.catch(err => console.log(err))

数据结构与算法

这一块在笔试、面试的代码题中考核较多,其中常考的数据结构主要有:数组、链表、队列、栈、Set、Map、哈希表等,不同数据结构有不同的方法以及储存原理,这些算是技术岗的必备知识。算法部分主要分为两大块,排序算法与一些其他算法题

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

排序算法根据考频高低主要有:快速排序、归并排序、堆排序、冒泡排序、插入排序、选择排序、希尔排序、桶排序、基数排序、Timsort这十种,这类考核点要么是算法的时间、空间复杂度、稳定度,要么是直接手写代码,故在理解算法原理的同时,对JS语言版的排序算法代码也要加强记忆。

  • 二叉树层序遍历
  • B 树的特性,B 树和 B+树的区别
  • 尾递归
  • 如何写一个大数阶乘?递归的方法会出现什么问题?
  • 把多维数组变成一维数组的方法
  • 知道的排序算法 说一下冒泡快排的原理
  • Heap 排序方法的原理?复杂度?
  • 几种常见的排序算法,手写
  • 数组的去重,尽可能写出多个方法
  • 如果有一个大的数组,都是整型,怎么找出最大的前 10 个数
  • 知道数据结构里面的常见的数据结构
  • 找出数组中第 k 大的数组出现多少次,比如数组【1,2, 4,4,3,5】第二大的数字是 4,出现两次,所以返回 2
  • 合并两个有序数组
  • 给一个数,去一个已经排好序的数组中寻找这个数的位 置(通过快速查找,二分查找)

[外链图片转存中…(img-4g9ZIkMd-1715010981007)]

  • 30
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值