TypeScript 基础集成
从 JavaScript 到 TypeScript:用静态类型提升代码质量与开发体验
学习目标
读完本文,你将学会:
- 理解 TypeScript 的类型系统和编译原理
- 掌握常用类型注解和接口定义
- 使用泛型编写可复用的类型安全代码
- 在现有 JavaScript 项目中渐进式引入 TypeScript
一、为什么要用 TypeScript
1.1 JavaScript 的类型问题
// 运行时才发现错误functionadd(a,b){returna+b;}add(1,2);// 3 ✅add('1','2');// '12' ❌ 逻辑错误,但语法合法add(1,'2');// '12' ❌ 更隐蔽的问题// 属性拼写错误constuser={name:'Alice'};console.log(user.nmae);// undefined,不报错1.2 TypeScript 的价值
JavaScript TypeScript ↓ ↓ 动态类型 静态类型检查 运行时错误 编译时捕获错误 IDE 提示弱 智能补全和重构 文档靠注释 类型即文档 重构风险高 安全重构1.3 TS 编译流程
TypeScript 源码 ↓ tsc 编译器 ↓ 类型检查 + 语法转换 ↓ JavaScript 目标代码(ES5/ES6/ESNext) ↓ 浏览器 / Node.js 运行二、基础类型
2.1 基本类型注解
// 原始类型letname:string='Alice';letage:number=25;letisActive:boolean=true;letempty:null=null;letnotDefined:undefined=undefined;// 任意类型(尽量少用)letanything:any=4;anything='string';anything=true;// 未知类型(类型安全的 any)letunknownValue:unknown=4;// unknownValue.toFixed(); // ❌ 错误:先需要类型断言或类型守卫if(typeofunknownValue==='number'){unknownValue.toFixed();// ✅}// 无返回值functionlogMessage(msg:string):void{console.log(msg);}// 永不返回functionthrowError(msg:string):never{thrownewError(msg);}2.2 数组与元组
// 数组letnumbers:number[]=[1,2,3];letnames:Array<string>=['Alice','Bob'];// 元组(固定长度和类型)letpoint:[number,number]=[10,20];letuserInfo:[string,number,boolean]=['Alice',25,true];// 只读数组constreadonlyArr:readonlynumber[]=[1,2,3];// readonlyArr.push(4); // ❌ 错误2.3 对象与接口
// 接口定义interfaceUser{id:number;name:string;email:string;age?:number;// 可选属性readonlycreatedAt:Date;// 只读属性}constuser:User={id:1,name:'Alice',email:'alice@example.com',createdAt:newDate()};// 索引签名interfaceDictionary{[key:string]:string;}constcolors:Dictionary={red:'#ff0000',green:'#00ff00'};// 类型别名typePoint={x:number;y:number};typeID=string|number;typeCallback=(data:string)=>void;三、联合类型与类型守卫
3.1 联合类型
functionformatInput(input:string|number):string{// 类型守卫if(typeofinput==='string'){returninput.trim();// TS 知道这里是 string}returninput.toFixed(2);// TS 知道这里是 number}3.2 类型收窄
interfaceBird{type:'bird';fly():void;}interfaceFish{type:'fish';swim():void;}typeAnimal=Bird|Fish;functionmove(animal:Animal){// 可辨识联合switch(animal.type){case'bird':animal.fly();// TS 知道是 Birdbreak;case'fish':animal.swim();// TS 知道是 Fishbreak;default:const_exhaustive:never=animal;// 穷尽检查}}四、泛型
4.1 基础泛型
// 泛型函数functionidentity<T>(value:T):T{returnvalue;}constnum=identity<number>(42);// numberconststr=identity('hello');// 类型推断为 string// 泛型接口interfaceContainer<T>{value:T;getValue():T;}constnumberBox:Container<number>={value:100,getValue(){returnthis.value;}};4.2 泛型约束
interfaceHasLength{length:number;}functionlogLength<TextendsHasLength>(arg:T):T{console.log(arg.length);returnarg;}logLength('hello');// ✅ string 有 lengthlogLength([1,2,3]);// ✅ 数组有 length// logLength(123); // ❌ number 没有 length4.3 泛型工具类型
// Partial: 所有属性可选interfaceUser{id:number;name:string;}typePartialUser=Partial<User>;// { id?: number; name?: string; }// Pick: 选取部分属性typeUserName=Pick<User,'name'>;// { name: string; }// Omit: 排除部分属性typeUserWithoutId=Omit<User,'id'>;// { name: string; }// Record: 键值对对象typePageInfo=Record<string,{title:string;path:string}>;// ReturnType: 提取函数返回类型functioncreateUser(){return{id:1,name:'Alice'};}typeNewUser=ReturnType<typeofcreateUser>;// Parameters: 提取函数参数类型typeCreateUserParams=Parameters<typeofcreateUser>;五、类与面向对象
interfaceAnimal{name:string;makeSound():void;}classDogimplementsAnimal{name:string;privateage:number;// 私有属性protectedbreed:string;// 受保护属性constructor(name:string,age:number,breed:string){this.name=name;this.age=age;this.breed=breed;}makeSound():void{console.log(`${this.name}says: Woof!`);}// GettergetisAdult():boolean{returnthis.age>=2;}// 静态方法staticcreatePuppy(name:string):Dog{returnnewDog(name,0,'Unknown');}}constdog=newDog('Buddy',3,'Golden Retriever');dog.makeSound();console.log(dog.isAdult);// true// console.log(dog.age); // ❌ 私有属性不可访问六、在 JS 项目中渐进式引入 TS
6.1 步骤
# 1. 安装 TypeScriptnpminstall--save-dev typescript# 2. 初始化配置npx tsc--init# 3. 重命名文件 .js → .ts(或保留 .js,添加 JSDoc 类型注释)# 4. 配置 tsconfig.json// tsconfig.json{"compilerOptions":{"target":"ES2020","module":"ESNext","moduleResolution":"node","strict":true,"esModuleInterop":true,"skipLibCheck":true,"forceConsistentCasingInFileNames":true,"outDir":"./dist","rootDir":"./src","declaration":true,"declarationMap":true,"sourceMap":true},"include":["src/**/*"],"exclude":["node_modules","dist","**/*.test.ts"]}6.2 JSDoc 类型注释(不修改 .js 文件)
/** * @param {string} name * @param {number} age * @returns {string} */functiongreet(name,age){return`Hello${name}, you are${age}`;}/** @type {import('./types').User} */constuser={id:1,name:'Alice'};二、常见误区与注意点
| 误区 | 正确做法 |
|---|---|
| TypeScript 在运行时做类型检查 | TS 类型只在编译时检查,运行时无类型信息 |
| any 可以随便用 | 尽量不用 any,使用 unknown + 类型守卫 |
| 接口和类型别名完全等价 | 接口可声明合并,类型别名更灵活 |
| 泛型越复杂越好 | 泛型应服务于复用,避免过度抽象 |
| 一次性全量迁移到 TS | 渐进式迁移,从核心模块开始 |
三、动手练习
练习 1:为 API 响应定义类型
为一组 REST API 接口定义请求参数和响应数据的 TypeScript 类型。
练习 2:实现泛型工具函数
实现一个deepClone泛型函数,支持深拷贝任意对象。
四、AI 辅助学习
4.1 本节知识点的 AI 提问模板
- “TypeScript 的 interface 和 type 有什么区别?”
- “如何实现一个类型安全的 EventEmitter?”
- “unknown 和 any 的区别是什么?”
4.2 警惕 AI 的常见错误
- AI 可能生成运行时依赖类型信息的代码(如
typeof variable === 'User') - AI 可能忘记 TS 类型擦除特性
五、配套代码
本文示例代码位于:CODE-ADVANCED/19-TypeScript基础集成/
| 文件名 | 说明 |
|---|---|
ts-basic.ts | 基础类型、接口、类型守卫 |
ts-advanced.ts | 泛型、工具类型、类与面向对象 |
ts-config-demo.json | tsconfig.json 推荐配置 |
六、本章小结
- TypeScript 在编译时进行类型检查,捕获潜在错误
- 接口、类型别名、联合类型构建类型系统基础
- 泛型让代码在保证类型安全的同时保持复用性
- 现有 JS 项目可通过渐进式迁移引入 TS
如果本文对你有帮助,欢迎点赞、收藏、关注专栏。有任何问题可以在评论区交流!