总结
技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
有了这些,我们现在就可以更新 package.json
的 scripts 来启动服务器。
- package.json
“scripts”: {
“build”: “tsc”,
“start”: “concurrently “tsc -w” “nodemon dist/js/app.js””
}
concurrently
帮助编译 TypeScript 代码,持续观察变化,同时启动服务器。也就是说,我们现在可以启动服务器了——但是,我们还没有创建一些有意义的东西。所以,让我们在下一节中解决这个问题。
创建 Todo 类型
- types/todo.ts
import { Document } from “mongoose”
export interface ITodo extends Document {
name: string
description: string
status: boolean
}
这里,我们有了继承 mongoose
提供的 Document
类型的 Todo 接口。稍后我们将使用它与 MongoDB 交互。也就是说,我们现在可以定义 Todo 模块。
创建 Todo 模块
- models/todo.ts
import { ITodo } from “./…/types/todo”
import { model, Schema } from “mongoose”
const todoSchema: Schema = new Schema(
{
name: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
status: {
type: Boolean,
required: true,
},
},
{ timestamps: true }
)
export default model(“Todo”, todoSchema)
首先导入 ITodo
接口和 一些 mongoose
导出的模块,后者是帮助定义 Todo schema 和在导出前把 ITodo 作为类型参数传入 model
。
这样,我们现在就可以在其他文件中使用 Todo 模块来与数据库交互。
创建 API 控制器
获取、新增、更新和删除 Todos
- controllers/todos/index.ts
import { Response, Request } from “express”
import { ITodo } from “./…/…/types/todo”
import Todo from “…/…/models/todo”
const getTodos = async (req: Request, res: Response): Promise => {
try {
const todos: ITodo[] = await Todo.find()
res.status(200).json({ todos })
} catch (error) {
throw error
}
}
这里,我们首先需要从 express
导入一些类型,因为我想显式指明类型。如果你想,你可以让 TypeScript 帮你推断。
接下来,我们使用 getTodos() 函数来获取数据,它接收 req
和 res
参数并返回 promise。
在前面创建的 Todo 模块的帮助下,我们现在可以从 MongoDB 获取数据并返回 Todo 数组。
- controllers/todos/index.ts
const addTodo = async (req: Request, res: Response): Promise => {
try {
const body = req.body as Pick<ITodo, “name” | “description” | “status”>
const todo: ITodo = new Todo({
name: body.name,
description: body.description,
status: body.status,
})
const newTodo: ITodo = await todo.save()
const allTodos: ITodo[] = await Todo.find()
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,该方法允许我们将数据保存到数据库。
ES6
-
列举常用的ES6特性:
-
箭头函数需要注意哪些地方?
-
let、const、var
-
拓展:var方式定义的变量有什么样的bug?
-
Set数据结构
-
拓展:数组去重的方法
-
箭头函数this的指向。
-
手写ES6 class继承。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
微信小程序
-
简单描述一下微信小程序的相关文件类型?
-
你是怎么封装微信小程序的数据请求?
-
有哪些参数传值的方法?
-
你使用过哪些方法,来提高微信小程序的应用速度?
-
小程序和原生App哪个好?
-
简述微信小程序原理?
-
分析微信小程序的优劣势
-
怎么解决小程序的异步请求问题?
rd–button">
<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,该方法允许我们将数据保存到数据库。
ES6
-
列举常用的ES6特性:
-
箭头函数需要注意哪些地方?
-
let、const、var
-
拓展:var方式定义的变量有什么样的bug?
-
Set数据结构
-
拓展:数组去重的方法
-
箭头函数this的指向。
-
手写ES6 class继承。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
微信小程序
-
简单描述一下微信小程序的相关文件类型?
-
你是怎么封装微信小程序的数据请求?
-
有哪些参数传值的方法?
-
你使用过哪些方法,来提高微信小程序的应用速度?
-
小程序和原生App哪个好?
-
简述微信小程序原理?
-
分析微信小程序的优劣势
-
怎么解决小程序的异步请求问题?