TypeScript 任务管理系统实战:从前端到后端(二十八)
- 后端开发
- 6天前
- 9热度
- 0评论
在现代 Web 开发中,TypeScript 已成为提升代码质量和开发效率的重要工具。本文将通过一个完整的任务管理系统项目,详细介绍如何在实际开发中应用 TypeScript。从项目结构设计到具体的功能实现,我们将逐步展示如何构建一个类型安全、高效且易于维护的应用程序。
为什么需要综合项目实战
学习 TypeScript 语法只是第一步,真正掌握这门语言需要通过实际项目来巩固知识。本教程将带你从零开始,构建一个完整的任务管理系统。这个项目不仅涵盖了前端(React + TypeScript)和后端(Node.js + Express + TypeScript)的开发,还展示了如何进行类型定义和状态管理。通过这个项目,你将能够:
- 掌握 TypeScript 在实际项目中的最佳实践
- 学会如何组织大型项目的文件结构
- 理解前后端类型定义的一致性
- 实现类型安全的 API 和状态管理
项目结构
为了更好地管理和维护代码,我们采用 Monorepo 风格来组织项目结构。Monorepo 允许我们将多个相关模块放在同一个仓库中,便于协同开发和版本控制。
task-manager/
├── client/ # 前端项目
│ ├── public/
│ ├── src/
│ └── vite.config.ts
├── server/ # 后端项目
│ ├── src/
│ └── tsconfig.json
└── package.json目录结构
每个子项目内部的目录结构按照功能进行划分,确保代码的高内聚低耦合。
client/
├── components/ # React 组件
├── hooks/ # 自定义 Hook
├── services/ # 服务层
├── types/ # 类型定义
├── App.tsx
└── index.tsx
server/
├── controllers/ # 控制器
├── models/ # 数据模型
├── routes/ # 路由
├── services/ # 服务层
├── types/ # 类型定义
└── app.ts类型定义
在 TypeScript 项目中,类型定义是确保代码质量的关键。我们首先定义项目的核心类型,包括任务的状态、优先级、任务接口等。
src/types/task.ts
// 任务状态枚举
export type TaskStatus = 'pending' | 'in-progress' | 'completed';
// 任务优先级枚举
export type TaskPriority = 'low' | 'medium' | 'high';
// 任务接口定义
export interface Task {
id: string; // 任务ID
title: string; // 任务标题
description?: string; // 任务描述(可选)
status: TaskStatus; // 任务状态
priority: TaskPriority; // 任务优先级
createdAt: string; // 创建时间
updatedAt: string; // 更新时间
dueDate?: string; // 截止日期(可选)
tags?: string[]; // 标签(可选)
}
// 创建任务的输入类型
export interface CreateTaskInput {
title: string;
description?: string;
priority: TaskPriority;
dueDate?: string;
tags?: string[];
}
// 更新任务的输入类型
export interface UpdateTaskInput {
title?: string;
description?: string;
status?: TaskStatus;
priority?: TaskPriority;
dueDate?: string;
tags?: string[];
}
// 任务过滤选项
export interface TaskFilter {
status?: TaskStatus;
priority?: TaskPriority;
search?: string;
}类型分层
将输入类型、输出类型和过滤类型分开定义,有助于提高代码的可维护性和可读性。例如,CreateTaskInput 和 UpdateTaskInput 分别用于创建和更新任务时的数据验证。
API 类型定义
为了确保前后端的一致性和类型安全,我们需要定义 API 相关的类型。
src/types/api.ts
// 通用 API 响应类型
export interface ApiResponse
<T> {
success: boolean;
data?: T;
error?: string;
message?: string;
}
// 分页元数据
export interface PaginationMeta {
total: number;
page: number;
pageSize: number;
totalPages: number;
}
// 分页响应类型
export interface PaginatedResponse
<T> {
items: T[];
meta: PaginationMeta;
}
// 请求错误类型
export interface ApiError {
code: string;
message: string;
details?: Record<string, string>;
}
// HTTP 方法类型
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
// 任务相关的 API 端点
export interface TaskEndpoints {
getAll: '/api/tasks';
getById: '/api/tasks/:id';
create: '/api/tasks';
update: '/api/tasks/:id';
delete: '/api/tasks/:id';
}API 类型
通过定义统一的响应格式和错误处理类型,我们可以简化前后端的对接过程,减少调试时间。
任务服务层
任务服务层负责实现任务管理的业务逻辑。我们将这些逻辑封装在一个服务类中,以便于测试和维护。
src/services/taskService.ts
// 导入类型定义
import { Task, CreateTaskInput, UpdateTaskInput, TaskFilter, TaskStatus, TaskPriority } from '../types/task';
// 生成唯一ID
function generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
// 模拟数据库(内存存储)
let tasks: Task[] = [
{
id: '1',
title: '学习 TypeScript',
description: '掌握 TypeScript 基础和高级特性',
status: 'completed',
priority: 'high',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
tags: ['学习', 'TypeScript']
},
{
id: '2',
title: '开发任务管理系统',
description: '使用 React + TypeScript 开发',
status: 'in-progress',
priority: 'high',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
tags: ['项目', '实战']
}
];
// 任务服务类
class TaskService {
// 获取所有任务
getAll(filter?: TaskFilter): Task[] {
let result = [...tasks];
if (filter) {
if (filter.status) {
result = result.filter(t => t.status === filter.status);
}
if (filter.priority) {
result = result.filter(t => t.priority === filter.priority);
}
if (filter.search) {
const search = filter.search.toLowerCase();
result = result.filter(t =>
t.title.toLowerCase().includes(search) ||
t.description?.toLowerCase().includes(search)
);
}
}
return result;
}
// 根据ID获取任务
getById(id: string): Task | undefined {
return tasks.find(t => t.id === id);
}
// 创建任务
create(input: CreateTaskInput): Task {
const now = new Date().toISOString();
const task: Task = {
id: generateId(),
title: input.title,
description: input.description,
status: 'pending',
priority: input.priority,
createdAt: now,
updatedAt: now,
dueDate: input.dueDate,
tags: input.tags
};
tasks.push(task);
return task;
}
// 更新任务
update(id: string, input: UpdateTaskInput): Task | null {
const index = tasks.findIndex(t => t.id === id);
if (index === -1) return null;
const task = tasks[index];
const updated: Task = {
...task,
...input,
updatedAt: new Date().toISOString()
};
tasks[index] = updated;
return updated;
}
// 删除任务
delete(id: string): boolean {
const index = tasks.findIndex(t => t.id === id);
if (index === -1) return false;
tasks.splice(index, 1);
return true;
}
// 更新任务状态
updateStatus(id: string, status: TaskStatus): Task | null {
return this.update(id, { status });
}
}
// 导出服务实例
export const taskService = new TaskService();服务层
将业务逻辑集中在服务层,不仅可以提高代码的可测试性,还可以方便地进行维护和扩展。
自定义 Hook
在 React 应用中,使用自定义 Hook 可以将状态管理和业务逻辑封装起来,使组件更加简洁和易于复用。
src/hooks/useTasks.ts
// 导入 React Hooks 和类型
import { useState, useEffect, useCallback } from 'react';
import { Task, CreateTaskInput, UpdateTaskInput, TaskFilter, TaskStatus, TaskPriority } from '../types/task';
import { taskService } from '../services/taskService';
// Hook 返回的状态类型
interface UseTasksReturn {
tasks: Task[];
loading: boolean;
error: string | null;
filter: TaskFilter;
createTask: (input: CreateTaskInput) => Promise
<void>;
updateTask: (id: string, input: UpdateTaskInput) => Promise
<void>;
deleteTask: (id: string) => Promise
<void>;
updateStatus: (id: string, status: TaskStatus) => Promise
<void>;
setFilter: (filter: TaskFilter) => void;
refresh: () => void;
}
// 初始化默认过滤器
const defaultFilter: TaskFilter = {};
export function useTasks(): UseTasksReturn {
// 任务列表状态
const [tasks, setTasks] = useState<Task[]>([]);
// 加载状态
const [loading, setLoading] = useState(true);
// 错误状态
const [error, setError] = useState<string | null>(null);
// 过滤条件
const [filter, setFilter] = useState
<TaskFilter>(defaultFilter);
// 加载任务列表
const loadTasks = useCallback(async () => {
setLoading(true);
setError(null);
try {
const data = await taskService.getAll(filter);
setTasks(data);
} catch (err) {
setError(err instanceof Error ? err.message : '加载失败');
} finally {
setLoading(false);
}
}, [filter]);
// 初始加载和过滤器变化时重新加载
useEffect(() => {
loadTasks();
}, [loadTasks]);
// 创建任务
const createTask = useCallback(async (input: CreateTaskInput) => {
try {
await taskService.create(input);
await loadTasks();
} catch (err) {
setError(err instanceof Error ? err.message : '创建失败');
}
}, [loadTasks]);
// 更新任务
const updateTask = useCallback(async (id: string, input: UpdateTaskInput) => {
try {
await taskService.update(id, input);
await loadTasks();
} catch (err) {
setError(err instanceof Error ? err.message : '更新失败');
}
}, [loadTasks]);
// 删除任务
const deleteTask = useCallback(async (id: string) => {
try {
await taskService.delete(id);
await loadTasks();
} catch (err) {
setError(err instanceof Error ? err.message : '删除失败');
}
}, [loadTasks]);
// 更新任务状态
const updateStatus = useCallback(async (id: string, status: TaskStatus) => {
try {
await taskService.updateStatus(id, status);
await loadTasks();
} catch (err) {
setError(err instanceof Error ? err.message : '状态更新失败');
}
}, [loadTasks]);
// 刷新任务列表
const refresh = useCallback(() => {
loadTasks();
}, [loadTasks]);
return {
tasks,
loading,
error,
filter,
createTask,
updateTask,
deleteTask,
updateStatus,
setFilter,
refresh
};
}自定义 Hook
通过将状态管理和业务逻辑封装在自定义 Hook 中,我们可以使组件更加简洁和易于维护。这种方式不仅提高了代码的可复用性,还减少了重复代码。
React 组件
在前端部分,我们将使用 React 组件来构建用户界面。每个组件都将利用 useTasks Hook 来管理任务状态和操作。
示例组件:src/components/TaskList.tsx
import React from 'react';
import { useTasks } from '../hooks/useTasks';
import { Task } from '../types/task';
const TaskList: React.FC = () => {
const { tasks, loading, error, createTask, updateTask, deleteTask, updateStatus, setFilter, refresh } = useTasks();
const handleCreateTask = async () => {
const input: CreateTaskInput = {
title: '新任务',
priority: 'medium'
};
await createTask(input);
};
const handleUpdateTask = async (id: string) => {
const input: UpdateTaskInput = {
title: '更新后的任务',
priority: 'high'
};
await updateTask(id, input);
};
const handleDeleteTask = async (id: string) => {
await deleteTask(id);
};
const handleUpdateStatus = async (id: string) => {
await updateStatus(id, 'completed');
};
if (loading) return
<div>加载中...</div>;
if (error) return
<div>{error}</div>;
return (
<div>
<button onClick={handleCreateTask}>创建任务</button>
<ul>
{tasks.map(task => (
<li key={task.id}>
<span>{task.title}</span>
<button onClick={() => handleUpdateTask(task.id)}>更新</button>
<button onClick={() => handleDeleteTask(task.id)}>删除</button>
<button onClick={() => handleUpdateStatus(task.id)}>完成</button>
</li>
))}
</ul>
</div>
);
};
export default TaskList;组件说明
在这个示例组件中,我们展示了如何使用 useTasks Hook 来管理任务列表的状态和操作。通过简单的按钮点击事件,我们可以创建、更新、删除和更改任务状态。
总结
通过本教程,我们详细介绍了如何使用 TypeScript 构建一个任务管理系统。从项目结构设计到具体的类型定义、服务层实现以及前端组件开发,每一个环节都遵循了最佳实践。希望这篇文章能帮助你在实际项目中更好地应用 TypeScript,提升代码质量和开发效率。如果你有任何问题或建议,欢迎在评论区留言交流。