当前位置: 首页 > news >正文

TypeScript总结:19、综合案例

TypeScript 综合案例:任务管理器

本文通过一个简单的任务管理器案例,把前面学习的枚举、接口、类、封装、泛型和严格类型检查串联起来。

一、案例目标

实现以下功能:

  • 创建任务。
  • 查询任务。
  • 完成任务。
  • 保存任务列表。
  • 使用泛型编写通用仓库。
  • 使用接口约束数据结构。
  • 使用类封装业务逻辑。

二、项目结构

为了方便学习,先把代码写在一个文件中:

ts-task/ ├── src/ │ └── index.ts ├── dist/ ├── package.json └── tsconfig.json

实际项目中,可以进一步拆分为:

src/ ├── models/ │ └── task.ts ├── repositories/ │ └── memory-repository.ts ├── services/ │ └── task-service.ts └── index.ts

三、定义任务状态

任务状态是有限的几个固定值,适合使用字符串枚举:

enumTaskStatus{Todo='todo',Doing='doing',Done='done'}

使用枚举后,任务状态不会被随意写成任意字符串:

conststatus:TaskStatus=TaskStatus.Todo;

四、定义任务接口

接口用于描述任务对象的结构:

interfaceTask{readonlyid:number;title:string;status:TaskStatus;}

id是任务唯一标识,创建后不应该被修改,所以使用readonly

五、定义泛型仓库接口

不同业务可能需要保存不同类型的数据,因此仓库可以使用泛型:

interfaceRepository<Textends{readonlyid:number}>{add(item:T):void;getById(id:number):T|undefined;list():T[];}

这里的泛型约束表示:传入仓库的数据必须具有只读的id数字属性。

六、实现内存仓库

使用类实现仓库接口:

classMemoryRepository<Textends{readonlyid:number}>implementsRepository<T>{privateitems:T[]=[];add(item:T):void{this.items.push(item);}getById(id:number):T|undefined{returnthis.items.find((item)=>item.id===id);}list():T[]{return[...this.items];}}

这里体现了几个知识点:

  • private:外部不能直接修改内部数组。
  • 泛型T:仓库可以保存不同类型的数据。
  • implements:类必须实现接口中声明的方法。
  • T | undefined:查询不到数据时明确表示可能没有结果。

七、封装任务服务

任务服务负责处理任务相关业务:

classTaskService{constructor(privatereadonlyrepository:Repository<Task>){}create(title:string):Task{consttask:Task={id:Date.now(),title,status:TaskStatus.Todo};this.repository.add(task);returntask;}complete(id:number):boolean{consttask=this.repository.getById(id);if(!task){returnfalse;}task.status=TaskStatus.Done;returntrue;}list():Task[]{returnthis.repository.list();}}

TaskService不需要知道任务具体保存在哪里,它只依赖Repository<Task>接口。这种设计方便以后把内存仓库替换成数据库仓库或接口仓库。

八、运行案例

constrepository=newMemoryRepository<Task>();consttaskService=newTaskService(repository);consttask=taskService.create('学习 TypeScript');console.log('创建任务:',task);taskService.complete(task.id);console.log('任务列表:',taskService.list());

预期结果类似:

创建任务: { id: 1690000000000, title: '学习 TypeScript', status: 'todo' } 任务列表: [ { id: 1690000000000, title: '学习 TypeScript', status: 'done' } ]

Date.now()的具体值会根据运行时间变化,上面的数字只是示例。

九、处理未知输入

如果任务状态来自用户输入或网络请求,不能直接断言为TaskStatus,应该先校验:

functionisTaskStatus(value:unknown):valueisTaskStatus{return(value===TaskStatus.Todo||value===TaskStatus.Doing||value===TaskStatus.Done);}functionparseTaskStatus(value:unknown):TaskStatus{if(isTaskStatus(value)){returnvalue;}returnTaskStatus.Todo;}

这里使用了:

  • unknown:表示外部传入的数据类型未知。
  • 类型谓词value is TaskStatus:告诉编译器检查成功后,值就是任务状态。
  • 默认值:输入不合法时回退到Todo

十、完整 index.ts

下面的代码可以直接保存为src/index.ts

enumTaskStatus{Todo='todo',Doing='doing',Done='done'}interfaceTask{readonlyid:number;title:string;status:TaskStatus;}interfaceRepository<Textends{readonlyid:number}>{add(item:T):void;getById(id:number):T|undefined;list():T[];}classMemoryRepository<Textends{readonlyid:number}>implementsRepository<T>{privateitems:T[]=[];add(item:T):void{this.items.push(item);}getById(id:number):T|undefined{returnthis.items.find((item)=>item.id===id);}list():T[]{return[...this.items];}}classTaskService{constructor(privatereadonlyrepository:Repository<Task>){}create(title:string):Task{consttask:Task={id:Date.now(),title,status:TaskStatus.Todo};this.repository.add(task);returntask;}complete(id:number):boolean{consttask=this.repository.getById(id);if(!task){returnfalse;}task.status=TaskStatus.Done;returntrue;}list():Task[]{returnthis.repository.list();}}constrepository=newMemoryRepository<Task>();consttaskService=newTaskService(repository);consttask=taskService.create('学习 TypeScript');console.log('创建任务:',task);taskService.complete(task.id);console.log('任务列表:',taskService.list());

十一、配置 tsconfig.json

{"compilerOptions":{"target":"ES2015","module":"CommonJS","rootDir":"src","outDir":"dist","strict":true,"noEmitOnError":true},"include":["src/**/*.ts"],"exclude":["node_modules","dist"]}

十二、运行项目

初始化项目并安装 TypeScript:

npminit-ynpminstall--save-dev typescript

编译:

npx tsc

运行编译结果:

nodedist/index.js

也可以在package.json中添加:

{"scripts":{"build":"tsc","start":"node dist/index.js","type-check":"tsc --noEmit"}}

十三、案例中的知识点对应关系

知识点案例中的使用位置
枚举TaskStatus
接口TaskRepository<T>
readonly任务id
MemoryRepositoryTaskService
private仓库内部的items
泛型Repository<T>MemoryRepository<T>
泛型约束T extends { readonly id: number }
unknown外部任务状态校验
严格检查strict: true

十四、可以继续扩展的功能

在这个案例上可以继续添加:

  1. 删除任务。
  2. 修改任务标题。
  3. 按状态筛选任务。
  4. 使用抽象类定义不同类型的仓库。
  5. 使用 Webpack 打包项目。
  6. 把内存仓库替换成数据库或 HTTP API。
  7. 为任务服务增加单元测试。

十五、本章小结

  • 接口负责描述结构。
  • 枚举负责约束有限状态。
  • 类负责封装数据和业务行为。
  • 泛型负责编写可复用的类型安全仓库。
  • unknown和类型谓词负责处理外部不可信输入。
  • tsconfig.json负责统一项目编译配置。
http://www.jsqmd.com/news/1397421/

相关文章:

  • 警惕广州黄金回收行业乱象!高位出金认准正规结算模式不亏价 - 资讯早知道
  • Tmux终端复用器:从会话持久化到高效多任务管理的完整指南
  • Autovisor智慧树刷课工具5分钟速通:从下载到自动学完一门课只需四步
  • Labelme标注转YOLOv5训练全流程实战指南
  • 智谱ZCode AI编程助手全面体验:从零上手到实战集成指南
  • MySQL到金仓:零日期与宽松模式数据清洗——历史系统非法日期迁移的检测、修复与回退
  • 2026安徽中考100-200分,安徽建工公办免学费学技能+升学历! - 小张zc
  • 2026安阳靠谱的汽车贴膜店精选实用指南 - 谁都没有我好看
  • 基于XyMediaVault构建不落盘家庭影音库:FUSE与WebDAV技术实践
  • 2026青岛黄金回收!5项服务标准,卖金更透明不踩坑 - 朝夕热点速报
  • Outlook已发送邮件丢失排查指南:从POP3/IMAP协议到数据文件修复
  • vbm: missing metadata. Full Image Restore with .vbk only?
  • 游戏AI智能教练:基于模仿学习与实时推荐的战术辅助系统
  • ubuntu安装django
  • 2026深圳搬家公司选型实用指南:福田搬南山收费拆解与本地品牌测评 - 深圳家顺兴搬家
  • 从零到实战:XUnity.AutoTranslator Unity游戏翻译插件终极上手指南
  • RAG技术演进:从古典检索到智能体化架构的工程实践
  • 免费又安全的本地视频字幕提取工具:3分钟把视频硬字幕一键转成SRT文件
  • 立足湾区二奢市场,深圳黄金回收落实新规,推动旧金回收走向标准化可溯源 - 日常前沿快讯
  • 迪奥和宝格丽在太行山下的变现路——焦作闲置奢侈品去哪卖 - 你就像风一样
  • 2026清河全屋整装拎包入住公司有哪些盘点 - 谁都没有我好看
  • Java RAG 实战(第 7 篇):使用 Qdrant 保存和检索向量
  • Lua脚本语言实战指南:从基础语法到模块化与性能优化
  • 酷睿Ultra+OLED+Wi-Fi7:宏碁“Googlebook”如何定义全能笔记本新基准
  • BetterJoy实战指南:Switch手柄在PC上一步到位的免费安装与配置教程
  • AI数字人唇形同步技术实践:从环境配置到批量生成全流程解析
  • HFSS仿真结果解读指南:从S参数到场分布,四步掌握电磁仿真分析
  • 开关电源PCB设计六大步骤
  • 【东莞市】2026CPPM证书报考价值详解|职场晋升+企业投标刚需认证报考全指南 - 中采供培
  • 普拉达和万国在卫水边的回收选择——新乡闲置名品去哪卖 - 你就像风一样