实战项目案例:编写任务管理系统的前端界面
下面编写任务管理系统的前端界面,如图23-2所示。它拥有添加任务、查看任务、设置任务为完成状态以及删除任务的功能。

在上一章里已经编写好了对应的 API,因此现在直接使用这些 API 即可。通过以下命令安装 Axios
库(Axios
是一个基于 promise
对象的网络请求库,可以用于浏览器和 Node.js),以便在前端项目中用 Axios
调用前面编写的后端 API。由于 Axios
已经内置 TypeScript 支持,因此无须再安装声明文件库。
$ npm install axios
使用上一节中的 React
项目结构,其中粗体字标出的文件表示相对于上一节新增或修改的文件。
D:\TSProject\client-side
│ ...
│
├─node_modules
│ ...
│
├─public
│ ...
│
└─src
│ apis.ts
│ App.css
│ App.test.tsx
│ App.tsx
│ index.css
│ index.tsx
│ logo.svg
│ react-app-env.d.ts
│ reportWebVitals.ts
│ setupTests.ts
│ type.d.ts
│
└─components
TaskCreator.tsx
TaskItem.tsx
编写任务类型声明及任务管理后端API
src/type.d.ts
文件的内容如下。
interface Task {
id: number,
name: string,
description: string,
isDone:boolean
}
该文件定义了表示任务的 Task
接口,供其他 TypeScript 文件引用,它包含 id
、name
(名称)、description
(描述)、isDone
(是否完成)等字段。
src/apis.ts
文件的内容如下。
import axios, { AxiosResponse } from "axios"
const baseUrl: string = "http://localhost:8000"
export const getTaskList = async (): Promise<AxiosResponse<Task[]>> => {
const tasks: AxiosResponse<Task[]> = await axios.get(
baseUrl + "/tasks"
)
return tasks;
}
export const addTask = async (task: Task): Promise<AxiosResponse<Task>> => {
const newTask: AxiosResponse<Task> = await axios.post(
baseUrl + "/task", task
);
return newTask;
}
export const deleteTask = async (taskId: number): Promise<AxiosResponse<boolean>> => {
const res: AxiosResponse<boolean> = await axios.delete(
baseUrl + "/task/" + taskId
);
return res;
}
export const setTaskDone = async (taskId: number): Promise<AxiosResponse<boolean>> => {
const res: AxiosResponse<boolean> = await axios.put(
baseUrl + "/task/" + taskId
);
return res;
}
该文件提供了访问任务管理后端 API 的功能,使用 Axios
调用前面编写的任务管理后端 API。
下面介绍 src/apis.ts
文件中部分方法的作用。
-
getTaskList() 方法:将调用后端API http://locahost:8000/tasks GET,获取全部任务列表。
-
addTask() 方法:将调用后端API http://locahost:8000/task POST,创建新的任务。
-
deleteTask() 方法:将调用后端API http://locahost:8000/task/:id DELETE,删除指定 id 的任务。
-
setTaskDone() 方法:将调用后端API http://locahost:8000/task/:id PUT,设置指定 id 的任务为完成状态。
编写添加任务UI组件及任务列表项UI组件
下面编写添加任务 UI
组件,如图23-3所示。

添加任务 UI
组件的文件为 src/components/TaskCreator.tsx
,内容如下。
import React, { useState } from 'react'
type Props = {
addTask: (e: React.FormEvent, formData: Task | any) => void
}
const TaskCreator: React.FC<Props> = ({ addTask }) => {
const [formData, setFormData] = useState<Task | {}>()
const handleForm = (e: React.FormEvent<HTMLInputElement>): void => {
setFormData({
...formData,
[e.currentTarget.id]: e.currentTarget.value,
})
}
return (
<form className='Form' onSubmit={(e) => addTask(e, formData)}>
<div>
<div>
<label htmlFor='name'>任务名称</label>
<input onChange={handleForm} type='text' id='name' />
</div>
<div>
<label htmlFor='description'>任务描述</label>
<input onChange={handleForm} type='text' id='description' />
</div>
</div>
<button disabled={formData === undefined ? true : false} >添加任务</button>
</form>
)
}
export default TaskCreator
该文件声明了一个函数式 UI 组件 TaskCreator
,允许通过传入 Props.addTask
接收添加任务的处理函数。
接下来,编写任务列表项 UI 组件,如图23-4所示。

编写任务列表项 UI 组件的文件为 src/components/TaskItem.tsx
,内容如下。
import React from 'react'
type Props = {
task: Task,
deleteTask: (id: number) => void,
setTaskDone: (id: number) => void
}
const TaskItem: React.FC<Props> = ({ task, deleteTask, setTaskDone }) => {
return (
<div className='Item'>
<div className='Item--text'>
<h1 className={task.isDone ? 'done-task' : ""}>{task.name}</h1>
<span className={task.isDone ? 'done-task' : ""}>{task.description}</span>
</div>
<div className='Item--button'>
<button
onClick={() => setTaskDone(task.id)}
className={task.isDone ? `hide-button` : "Item--button__done"}
>
完成
</button>
<button
onClick={() => deleteTask(task.id)}
className='Item--button__delete'
>
删除
</button>
</div>
</div>
)
}
export default TaskItem
该文件声明了一个函数式 UI 组件 TaskItem
,允许传入 Props.task
来接收当前任务数据,允许传入 Props.deleteTask
来接收删除任务的处理函数,允许传入 Props.setTaskDone
来接收设置任务为完成状态的处理函数。
在 TaskItem
组件中,根据任务的完成状态(task.isDone
),决定是否显示 “完成” 按钮,以及是否在任务名称和描述上增加删除线。当 task.isDone
为 true
时,完成状态的任务列表项 UI 组件如图23-5所示。

编写任务管理页面及样式
下面编写任务管理系统的前端界面,并将添加任务 UI 组件及任务列表项 UI 组件组合起来。任务管理页面的文件为 src/App.tsx
,内容如下。
import React, { useEffect, useState } from 'react'
import TaskCreator from './components/TaskCreator'
import TaskItem from './components/TaskItem'
import { addTask, getTaskList, deleteTask, setTaskDone } from './apis'
const App: React.FC = () => {
const [tasks, setTasks] = useState<Task[]>([])
useEffect(() => {
getTaskList().then(p => setTasks(p.data));
}, [])
const handleAddTask = (e: React.FormEvent, formData: Task): void => {
addTask(formData).then(p => setTasks([...tasks, p.data]));
}
const handleDeleteTask = (id: number): void => {
deleteTask(id).then(p => {
let deletedTaskIndex = tasks.findIndex(y => y.id == id);
let newTasks = [...tasks]
newTasks.splice(deletedTaskIndex, 1);
setTasks(newTasks);
}
)
}
const handleSetTaskDone = (id: number): void => {
setTaskDone(id).then(p => {
let doneTaskIndex = tasks.findIndex(y => y.id == id);
tasks[doneTaskIndex].isDone = true;
setTasks([...tasks]);
}
)
}
return (
<main className='App'>
<h1>任务管理</h1>
<TaskCreator addTask={handleAddTask} />
<div className='Item'>
<h1>全部任务</h1>
</div>
{tasks.map((task: Task) => (
<TaskItem
key={task.id}
task={task}
deleteTask={handleDeleteTask}
setTaskDone={handleSetTaskDone}
/>
))}
</main>
)
}
export default App
接下来分别介绍 src/App.tsx
文件中部分代码的作用。
-
const [tasks, setTasks]:声明 tasks 变量和 setTasks() 函数,将返回一个名为 task 的 State 变量,以及一个更新 State 的函数 setTasks,参数类型为 Task[]。
-
useEffect(…) 方法:在浏览器中执行的代码,该方法将会调用后端 API 获取全部任务列表数据,并将任务状态设置到 State 中。
-
handleAddTask(…) 方法:添加任务的方法,该方法将会调用后端 API 添加任务,并将新任务设置到 State 中。
-
handleDeleteTask(…) 方法:删除任务的方法,该方法将会调用后端 API 删除任务,并将删除此任务后的任务列表的状态设置到 State 中。
-
handleSetTaskDone(…) 方法:设置任务完成的方法,该方法将会调用后端 API 将任务设置为已完成状态,并将此状态设置到 State 中。
-
return 语句:返回整个页面的元素结构,其中包含添加任务 UI 组件及遍历全部任务后多次渲染的列表项 UI 组件。
接下来,编写整个页面的样式,样式文件为 src/index.css
,内容如下。
* {
margin: 0;
padding: 0;
}
body {
color: #fff;
background: #333;
}
.App {
max-width: 600px;
margin: auto;
}
.App>h1 {
text-align: center;
margin: 10px;
}
.Item {
display: flex;
justify-content: space-between;
align-items: center;
background: #444;
padding: 10px;
border-bottom: 1px solid #333333;
}
.Item--text h1 {
color: #f59609;
}
.Item--button button {
background: #ffffff;
padding: 10px;
border-radius: 20px;
cursor: pointer;
}
.Item--button__delete {
border: 1px solid #ce0404;
color: #ce0404;
}
.Item--button__done {
border: 1px solid #05b873;
color: #05b873;
margin-right: 10px;
}
.hide-button {
display: none;
}
.done-task {
text-decoration: line-through;
color: #777 !important;
}
.Form {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background: #444;
margin-bottom: 15px;
}
.Form>div {
display: flex;
justify-content: center;
align-items: center;
}
.Form input {
background: #ffffff;
padding: 10px;
border: 1px solid #f59609;
border-radius: 10px;
display: block;
margin: 5px;
}
.Form button {
background: #f59609;
color: #fff;
padding: 10px;
border-radius: 20px;
cursor: pointer;
border: none;
}
现在就可以在项目目录下(本例中为 D:\TSProject\client-side)执行 npm start
命令,启动前端 UI 应用程序了。注意,在启动 UI 应用程序之前,需要先启动前面创建的后端服务。UI 应用程序启动后,就可以在浏览器地址栏中输入 http://localhost:3000 ,访问任务管理页面,并在任务管理页面中执行相应操作,如图23-6所示。
