TypeScript(简称 TS)是 JavaScript 的强类型超集,在 JS 基础上增加了静态类型系统,可编译为纯 JS 运行于任何支持 JS 的环境。核心价值是类型校验(提前发现错误)、代码提示(提升开发效率)、工程化支撑(适配大型项目),目前已成为 Vue3、React、Node.js 等生态的主流开发语言。
一、TypeScript 基础入门
1. 环境搭建与编译
1.1 安装 TS
通过 npm/yarn 全局安装 TypeScript 编译器(tsc):
# npm 安装npminstalltypescript -g# yarn 安装yarnglobaladdtypescript1.2 初始化 TS 配置
在项目根目录执行tsc --init,生成默认配置文件tsconfig.json(核心配置后续详解),关键默认配置:
{"compilerOptions":{"target":"ES2016",// 编译后 JS 版本(ES3/ES5/ES6+)"module":"CommonJS",// 模块规范(CommonJS/ESNext/AMD)"outDir":"./dist",// 编译后 JS 文件输出目录"rootDir":"./src",// TS 源码目录"strict":true,// 开启严格模式(强烈推荐,强制类型校验)"esModuleInterop":true// 兼容 CommonJS 和 ESM 模块},"include":["./src/**/*"],// 需要编译的 TS 文件"exclude":["node_modules"]// 排除的目录}1.3 编译运行 TS
- 新建
src/index.ts(TS 源码文件),编写代码后执行编译:tsc# 执行编译(按 tsconfig.json 配置编译所有 TS 文件)tsc src/index.ts# 单独编译指定 TS 文件tsc --watch# 监听 TS 文件变化,自动重新编译(开发必备) - 编译后会在
dist目录生成对应index.js,直接运行 JS 文件即可:node dist/index.js
2. 核心基础类型(最常用,必须掌握)
TS 提供 10+ 基础类型,覆盖 JS 原生类型 + 新增类型,变量声明时需指定类型(或让 TS 自动推导),赋值时类型必须匹配,否则编译报错。
2.1 基本原生类型(对应 JS 原生类型)
| 类型 | 说明 | 示例代码 |
|---|---|---|
string | 字符串类型 | let name: string = "张三"; const msg: string =Hello ${name}; |
number | 数字类型(整数/浮点数) | let age: number = 25; const pi: number = 3.14; const hex: number = 0x10; |
boolean | 布尔类型 | let isDone: boolean = true; const isLoading: boolean = false; |
null | 空值类型 | let n: null = null; |
undefined | 未定义类型 | let u: undefined = undefined; |
symbol | 唯一值类型(ES6+) | const id: symbol = Symbol("id"); const id2: symbol = Symbol("id");(id≠id2) |
bigint | 大整数类型(ES11+) | let bigNum: bigint = 100n; const max: bigint = 9007199254740991n; |
2.2 TS 新增基础类型
| 类型 | 说明 | 示例代码 |
|---|---|---|
any | 任意类型(关闭类型校验,尽量少用) | let val: any = 123; val = "abc"; val = true;(无类型报错) |
unknown | 未知类型(安全版 any,需先校验类型才能使用) | let unk: unknown = "hello"; if (typeof unk === "string") { console.log(unk.length); } |
void | 无返回值类型(常用于函数返回值) | function log(): void { console.log("日志"); }(函数无 return 或 return undefined) |
never | 永无返回值类型(函数永远不会执行完,如抛出错误、无限循环) | function throwErr(): never { throw new Error("报错"); } function loop(): never { while(true) {} } |
2.3 类型推导(TS 自动识别类型,简化代码)
若变量声明时直接赋值,TS 会自动推导变量类型,无需手动指定(推荐优先用类型推导,减少冗余):
// 自动推导为 string 类型letname="李四";// name = 123; // 报错:类型 "number" 不能赋值给类型 "string"// 自动推导为 number 类型letage=30;// 函数返回值自动推导为 number 类型functionadd(a:number,b:number){returna+b;}3. 数组类型(3 种声明方式)
数组元素类型需统一,TS 提供 3 种简洁的声明方式,优先用前两种:
// 方式 1:类型 + [](最常用)letarr1:number[]=[1,2,3];// 数字数组letarr2:string[]=["a","b","c"];// 字符串数组// arr1.push("4"); // 报错:不能将类型 "string" 分配给类型 "number"// 方式 2:Array<类型>(泛型写法,后续详解泛型)letarr3:Array<boolean>=[true,false];// 布尔数组letarr4:Array<number>=[4,5,6];// 方式 3:联合类型数组(数组元素可多种类型,后续详解联合类型)letarr5:(number|string)[]=[1,"a",2,"b"];// 数字或字符串数组4. 元组类型(Tuple,固定长度+固定类型的数组)
元组是特殊的数组,需明确指定「每个位置的元素类型」和「数组长度」,赋值时类型、顺序、长度必须完全匹配(常用于多返回值场景):
// 声明元组:第一个元素 string,第二个 number,第三个 booleanlettuple:[string,number,boolean]=["张三",25,true];// 正确取值(类型自动推导)letname=tuple[0];// string 类型letage=tuple[1];// number 类型// 错误用法(编译报错)// tuple = ["李四", "30", true]; // 第二个元素应为 number,实际是 string// tuple = ["李四", 30]; // 长度应为 3,实际是 2// tuple.push("额外元素"); // 严格模式下报错,非严格模式允许(不推荐)// 实战场景:函数返回多类型值functiongetUserInfo():[string,number]{return["张三",25];}const[userName,userAge]=getUserInfo();// 解构赋值,类型自动匹配二、TypeScript 进阶类型(项目核心)
进阶类型是 TS 工程化能力的核心,覆盖「复杂数据结构描述」「类型复用」「类型约束」等场景,掌握后可应对 90% 项目需求。
1. 联合类型(Union Type,|连接多个类型)
表示变量/参数可以是「多个类型中的任意一种」,使用时需通过「类型守卫」(判断类型)避免报错:
// 1. 基础用法:变量可以是 number 或 string 类型letunionVal:number|string;unionVal=123;// 合法unionVal="abc";// 合法// unionVal = true; // 报错:类型 "boolean" 不在联合类型中// 2. 函数参数联合类型(常用场景)functionformatValue(val:number|string):string{// 类型守卫:判断 val 是 string 类型(避免调用 length 时报错)if(typeofval==="string"){returnval.toUpperCase();// 此时 TS 确定 val 是 string,可调用 string 方法}// 否则 val 是 number 类型returnval.toFixed(2);// 调用 number 方法}console.log(formatValue(3.1415));// "3.14"console.log(formatValue("hello"));// "HELLO"// 3. 联合类型 + 字面量类型(精准约束取值范围,常用)typeStatus="success"|"error"|"loading";// 字面量联合类型(后续详解 type)letrequestStatus:Status;requestStatus="success";// 合法requestStatus="error";// 合法// requestStatus = "pending"; // 报错:不在 Status 类型范围内2. 交叉类型(Intersection Type,&合并多个类型)
表示将「多个类型的属性/方法合并为一个新类型」,新类型包含所有原类型的成员(常用于对象类型合并):
// 1. 基础用法:合并两个对象类型typePerson={name:string;age:number};typeJob={job:string;salary:number};// 交叉类型:同时拥有 Person 和 Job 的所有属性typePersonWithJob=Person&Job;// 赋值时必须包含所有属性constperson:PersonWithJob={name:"张三",age:25,job:"前端开发",salary:20000};// 2. 函数参数交叉类型(合并多个参数类型约束)typeBaseParams={id:number};typePageParams={page:number;size:number};functionfetchData(params:BaseParams&PageParams){console.log(params.id,params.page,params.size);}fetchData({id:1,page:1,size:10});// 合法// 3. 注意:基本类型交叉无意义(会变成 never 类型)typeNeverType=number&string;// never 类型(无任何值能同时是 number 和 string)3. 类型别名(Type Alias,type关键字,复用类型)
用type给「任意类型」起别名,简化复杂类型的使用,支持复用和组合(核心作用:类型复用):
// 1. 基础类型别名(简化重复类型)typeStrType=string;typeNumType=number;letstr:StrType="hello";letnum:NumType=123;// 2. 复杂类型别名(常用场景:联合类型、对象类型、元组类型)// 联合类型别名(约束状态值)typeStatus="success"|"error"|"loading";// 对象类型别名(描述用户数据结构)typeUser={id:number;name:string;age?:number;// 可选属性(后续详解)readonlyphone:string;// 只读属性(后续详解)};// 元组类型别名(描述坐标)typePoint=[number,number];// 3. 类型别名组合(复用已有类型)typeUserWithAddress=User&{address:string};// 交叉合并constuser:UserWithAddress={id:1,name:"张三",phone:"13800138000",address:"北京市"};// 4. 函数类型别名(描述函数结构,后续详解函数类型)typeFnType=(a:number,b:number)=>number;constadd:FnType=(x,y)=>x+y;4. 接口(Interface,interface关键字,描述对象/类结构)
interface主要用于「描述对象的属性结构」或「类的抽象结构」,功能与type类似,但更侧重「结构约束」,支持「继承」和「扩展」:
// 1. 基础用法:描述对象结构(与 type 类似)interfacePerson{name:string;// 必选属性age?:number;// 可选属性(? 表示可填可不填)readonlyid:number;// 只读属性(初始化后不能修改)[key:string]:any;// 任意属性(允许添加未定义的属性,key 为 string 类型,值为 any 类型)}// 赋值时:必选属性必须填,可选属性可省略,任意属性可额外添加constp1:Person={id:1,name:"张三",// age: 25, // 可选,可省略gender:"男"// 任意属性,合法};// p1.id = 2; // 报错:只读属性不能修改p1.age=26;// 可选属性可修改// 2. 接口继承(核心优势:复用已有接口,扩展新属性)interfaceStudentextendsPerson{studentId:number;// 新增学生专属属性study():void;// 新增学生专属方法(无返回值)}// 赋值时需包含 Person + Student 的所有属性/方法conststudent:Student={id:2,name:"李四",studentId:1001,study(){console.log("学习 TypeScript");}};// 3. 接口扩展(合并多个接口,与交叉类型类似)interfaceA{a:number}interfaceB{b:string}interfaceCextendsA,B{c:boolean}// 继承 A 和 Bconstobj:C={a:1,b:"2",c:true};// 4. 接口描述函数类型(与 type 类似,少用,优先用 type)interfaceFnInterface{(a:number,b:number):number;}constmultiply:FnInterface=(x,y)=>x*y;5.typevsinterface(关键区别,避免混淆)
两者功能高度重合(都能描述对象/函数类型),但有核心区别,项目中需合理选择:
| 对比维度 | type(类型别名) | interface(接口) |
|---|---|---|
| 核心用途 | 给任意类型起别名(基础类型、联合、交叉等) | 描述对象/类的结构,侧重「结构约束」 |
| 继承能力 | 无直接继承,需用交叉类型(&)实现合并 | 支持extends继承,结构更清晰 |
| 合并能力 | 不支持重复定义合并(重复定义会报错) | 支持重复定义合并(多个同名接口自动合并属性) |
| 支持类型 | 支持所有类型(基础、联合、交叉、元组等) | 主要支持对象/类/函数类型,不支持基础类型别名 |
| 实战选择 | 1. 基础类型/联合/交叉/元组的别名;2. 函数类型 | 1. 对象结构描述;2. 类的抽象结构(继承场景) |
实战示例:重复定义合并(interface 独有)
// interface 重复定义,自动合并属性interfaceUser{id:number;name:string}interfaceUser{age?:number;gender:string}// 最终 User 接口:{ id: number; name: string; age?: number; gender: string }constuser:User={id:1,name:"张三",gender:"男"};// type 重复定义,直接报错typeUserType={id:number};// type UserType = { name: string }; // 报错:标识符 "UserType" 重复6. 函数类型(约束函数的参数和返回值)
TS 对函数的约束包括「参数类型」「参数个数」「返回值类型」,支持「可选参数」「默认参数」「剩余参数」等场景:
6.1 基础函数类型声明
// 方式 1:直接声明(最常用)// 参数 a/b 为 number 类型,返回值为 number 类型(返回值可省略,TS 自动推导)functionadd(a:number,b:number):number{returna+b;}// 方式 2:类型别名声明(复用函数类型)typeFn=(x:number,y:number)=>number;constsubtract:Fn=(x,y)=>x-y;// 方式 3:接口声明(少用,优先用 type)interfaceMultiplyFn{(x:number,y:number):number;}constmultiply:MultiplyFn=(x,y)=>x*y;6.2 可选参数与默认参数
- 可选参数:用
?标记,必须放在「必选参数后面」; - 默认参数:参数赋值默认值,TS 会自动推导类型,默认参数可放在必选参数前面(但调用时需显式传
undefined跳过);
// 1. 可选参数(age 可选,放在必选参数 name 后面)functiongetUser(name:string,age?:number):string{returnage?`${name},${age}岁`:name;}getUser("张三");// 合法(省略可选参数)getUser("张三",25);// 合法// 2. 默认参数(age 有默认值,类型自动推导为 number)functiongreet(name:string,age:number=18):string{return`Hello${name},${age}岁`;}greet("李四");// 合法(使用默认值 18)greet("李四",30);// 合法(覆盖默认值)// 3. 默认参数在前(需显式传 undefined 跳过)functionfn(a:number=10,b:number):number{returna+b;}fn(undefined,20);// 合法(a 用默认值 10,b 传 20)6.3 剩余参数(...接收多个参数,转为数组)
剩余参数必须是「最后一个参数」,类型需声明为数组类型:
// 剩余参数 nums 为 number 数组,返回所有数字的和functionsum(...nums:number[]):number{returnnums.reduce((total,num)=>total+num,0);}console.log(sum(1,2,3));// 6console.log(sum(4,5,6,7));// 22// 剩余参数 + 固定参数functionlogInfo(name:string,...details:string[]):void{console.log(`姓名:${name},详情:${details.join(", ")}`);}logInfo("张三","25岁","前端开发");// 姓名:张三,详情:25岁, 前端开发6.4 无返回值函数(void类型)
函数无return或return undefined时,返回值类型为void(可省略,TS 自动推导):
// 显式声明 voidfunctionlogMsg(msg:string):void{console.log(msg);// return; // 合法(无返回值)// return undefined; // 合法(返回 undefined,void 允许)// return 123; // 报错:不能返回 number 类型}// 省略返回值类型,TS 自动推导为 voidfunctionalertMsg(msg:string){alert(msg);}7. 泛型(Generic,<T>,类型复用+动态类型约束)
泛型是 TS 最强大的特性之一,核心作用是「让类型可复用、可动态传递」,解决「重复定义相似类型」和「类型与值关联」的问题(比如:数组、函数、组件的通用类型约束)。
7.1 泛型基础:函数泛型(最常用场景)
需求:编写一个「返回传入值本身」的函数,支持任意类型(number/string/对象等),且返回值类型与传入值一致。
// 1. 泛型函数定义:<T> 是泛型参数(T 是占位符,可自定义名称,如 <K> <U>)// T 会动态接收传入参数的类型,参数 val 类型为 T,返回值类型也为 Tfunctionidentity<T>(val:T):T{returnval;}// 2. 调用泛型函数(2 种方式)// 方式 1:显式指定泛型类型(<number> 表示 T=number)constnum=identity<number>(123);// num 类型为 number// 方式 2:TS 自动推导泛型类型(推荐,简化代码)conststr=identity("abc");// 自动推导 T=string,str 类型为 stringconstobj=identity({name:"张三"});// 自动推导 T=对象类型// 3. 多泛型参数(多个动态类型传递)// T 表示第一个参数类型,U 表示第二个参数类型,返回值为 [T, U] 元组类型functionmerge<T,U>(a:T,b:U):[T,U]{return[a,b];}// 自动推导 T=number,U=string,返回值类型为 [number, string]constmerged=merge(123,"abc");const[numVal,strVal]=merged;// numVal: number,strVal: string7.2 泛型约束(extends,限制泛型的范围)
泛型默认支持任意类型,若需「约束泛型必须包含某个属性/方法」,需用extends实现泛型约束:
// 需求:函数接收一个对象,返回对象的 length 属性(需确保对象有 length 属性)interfaceHasLength{length:number;// 约束泛型必须有 length 属性}// 泛型约束:T extends HasLength → T 必须是 HasLength 的子类型(包含 length 属性)functiongetLength<TextendsHasLength>(val:T):number{returnval.length;}// 合法调用(传入值有 length 属性)getLength("abc");// 字符串有 length,返回 3getLength([1,2,3]);// 数组有 length,返回 3getLength({length:5});// 对象有 length,返回 5// 报错(传入值无 length 属性)// getLength(123); // number 类型无 length 属性// getLength(true); // boolean 类型无 length 属性7.3 泛型接口/类型别名(复用泛型对象类型)
用泛型定义接口/类型别名,实现「通用对象类型复用」:
// 1. 泛型类型别名(描述通用数组/对象)// 通用数组类型(等价于 T[])typeGenericArray<T>=Array<T>;constnumArr:GenericArray<number>=[1,2,3];// 数字数组conststrArr:GenericArray<string>=["a","b","c"];// 字符串数组// 通用对象类型(键为 string,值为 T 类型)typeGenericObj<T>={[key:string]:T};constobj1:GenericObj<number>={a:1,b:2};// 值为 numberconstobj2:GenericObj<string>={name:"张三",age:"25"};// 值为 string// 2. 泛型接口(描述通用函数/对象)interfaceGenericFn<T,U>{(param:T):U;// 接收 T 类型参数,返回 U 类型值}// 实现泛型接口:T=number,U=stringconstnumToString:GenericFn<number,string>=(num)=>num.toString();numToString(123);// 返回 "123",类型为 string7.4 泛型默认值(给泛型参数设置默认类型)
泛型参数可设置默认值,若调用时未指定且无法推导,则使用默认类型:
// 泛型默认值:T 默认为 string 类型functioncreateArray<T=string>(length:number,val:T):T[]{returnArray(length).fill(val);}// 1. 未指定泛型,自动推导 T=number(val 是 number 类型)constarr1=createArray(3,123);// number[] 类型,[123, 123, 123]// 2. 未指定泛型,无法推导(val 未传?不,这里 val 必传,换个例子)// 泛型默认值示例 2:T 默认为 numbertypeDefaultGeneric<T=number>={value:T};constobj1:DefaultGeneric={value:123};// T=number(使用默认值)constobj2:DefaultGeneric<string>={value:"abc"};// T=string(指定类型)8. 类型守卫(Type Guard,缩小类型范围)
类型守卫是「在运行时判断变量类型」的逻辑,让 TS 在编译时就能确定变量的具体类型,避免联合类型使用时的报错(核心:通过判断,让 TS 识别更精准的类型)。
8.1 常用类型守卫方式
| 类型守卫方式 | 适用场景 | 示例代码 |
|---|---|---|
typeof类型守卫 | 基础类型(string/number/boolean/symbol) | if (typeof val === "string") { /* val 是 string 类型 */ } |
instanceof类型守卫 | 引用类型(数组/对象/类实例) | if (val instanceof Array) { /* val 是数组类型 */ } |
in类型守卫 | 对象是否包含某个属性 | if ("age" in val) { /* val 包含 age 属性 */ } |
| 字面量类型守卫 | 联合字面量类型判断 | if (status === "success") { /* status 是 "success" 类型 */ } |
| 自定义类型守卫 | 复杂类型判断(自定义逻辑) | function isUser(val: any): val is User { return "id" in val && "name" in val; } |
8.2 实战示例
// 1. typeof 类型守卫(基础类型判断)functionhandleValue(val:number|string|boolean){if(typeofval==="string"){console.log(val.length);// val 是 string 类型,可调用 length}elseif(typeofval==="number"){console.log(val.toFixed(2));// val 是 number 类型,可调用 toFixed}else{console.log(val?"true":"false");// val 是 boolean 类型}}// 2. instanceof 类型守卫(引用类型判断)classAnimal{name:string;constructor(name:string){this.name=name;}}classDogextendsAnimal{bark(){console.log("汪汪");}}classCatextendsAnimal{meow(){console.log("喵喵");}}functionanimalSound(animal:Animal){if(animalinstanceofDog){animal.bark();// animal 是 Dog 类型,可调用 bark 方法}elseif(animalinstanceofCat){animal.meow();// animal 是 Cat 类型,可调用 meow 方法}}animalSound(newDog("旺财"));// 汪汪// 3. in 类型守卫(对象属性判断)typeUser={id:number;name:string};typeProduct={id:number;price:number};functiongetInfo(item:User|Product){if("name"initem){console.log(`用户:${item.name}`);// item 是 User 类型}else{console.log(`商品价格:${item.price}`);// item 是 Product 类型}}// 4. 自定义类型守卫(复杂类型判断)// 自定义函数,返回值为 `val is User`(类型谓词),表示若返回 true,则 val 是 User 类型functionisUser(val:any):valisUser{// 判断逻辑:val 是对象,且包含 id 和 name 属性,且 id 是 number 类型returntypeofval==="object"&&val!==null&&"id"inval&&"name"inval&&typeofval.id==="number";}functionprintUser(val:any){if(isUser(val)){console.log(val.id,val.name);// TS 确定 val 是 User 类型,无报错}else{console.log("不是 User 类型");}}printUser({id:1,name:"张三"});// 1 张三printUser({price:100});// 不是 User 类型9. 索引类型(Index Type,操作对象的属性名/属性值类型)
索引类型用于「动态获取对象的属性名类型」或「属性值类型」,核心语法:
keyof T:获取对象类型T的所有属性名组成的联合类型(索引签名);T[K]:获取对象类型T中,属性名类型为K的属性值类型(索引访问);
9.1 基础用法:keyof与T[K]
// 定义对象类型typeUser={id:number;name:string;age:number;};// 1. keyof T:获取 User 的所有属性名组成的联合类型 → "id" | "name" | "age"typeUserKeys=keyofUser;constkey1:UserKeys="id";// 合法constkey2:UserKeys="name";// 合法// const key3: UserKeys = "gender"; // 报错:不在属性名联合类型中// 2. T[K]:获取属性值类型(K 是属性名类型)// 获取 User 中 "id" 属性的类型 → numbertypeIdType=User["id"];// 获取 User 中 "name" 属性的类型 → stringtypeNameType=User["name"];// 获取 User 中多个属性的类型 → number | stringtypeMixedType=User["id"|"name"];// 3. 泛型 + 索引类型(实战场景:安全获取对象属性)// 函数功能:获取对象 obj 中 key 对应的属性值,确保 key 是 obj 的合法属性functiongetProp<T,KextendskeyofT>(obj:T,key:K):T[K]{returnobj[key];}constuser:User={id:1,name:"张三",age:25};// 合法调用:key 是 User 的属性名,返回值类型自动匹配constuserId=getProp(user,"id");// number 类型constuserName=getProp(user,"name");// string 类型// 报错:key 不是 User 的属性名(TS 编译时拦截错误,避免运行时 bug)// const userGender = getProp(user, "gender");9.2 索引签名(描述任意属性的对象类型)
当对象属性不固定时,用索引签名描述「属性名类型」和「属性值类型」,支持两种索引:
- 字符串索引:
[key: string]: 类型(属性名是 string 类型); - 数字索引:
[key: number]: 类型(属性名是 number 类型,常用于数组);
// 1. 字符串索引签名(属性名是 string,属性值是 number)typeNumObj={[key:string]:number;};constobj1:NumObj={a:1,b:2,c:3};// 合法// const obj2: NumObj = { a: 1, b: "2" }; // 报错:属性值必须是 number// 2. 混合索引签名(固定属性 + 任意属性)typeUser={id:number;// 固定属性(类型必须兼容任意属性的类型)[key:string]:number|string;// 任意属性:值可以是 number 或 string};constuser:User={id:1,// 固定属性(number 兼容任意属性类型)name:"张三",// 任意属性(string 类型)age:25// 任意属性(number 类型)};// 3. 数字索引签名(常用于数组,等价于 T[])typeNumArray={[key:number]:number;};constarr:NumArray=[1,2,3];// 合法(数组索引是 number 类型,值是 number 类型)arr[0]=4;// 合法// arr[0] = "4"; // 报错:值必须是 number 类型10. 映射类型(Mapped Type,基于已有类型生成新类型)
映射类型是「基于已有类型,批量修改属性的类型或修饰符」,核心语法:{ [K in keyof T]: 新类型 },本质是「遍历已有类型的属性,生成新类型」(TS 内置了多个常用映射类型,也支持自定义)。
10.1 基础自定义映射类型
// 原始类型typeUser={id:number;name:string;age:number;};// 1. 基础映射:遍历 User 的所有属性,属性值类型改为 string(批量修改类型)typeUserToString={[KinkeyofUser]:string;// K 遍历 keyof User("id" | "name" | "age"),值类型改为 string};// UserToString 等价于:{ id: string; name: string; age: string }constuser1:UserToString={id:"1",name:"张三",age:"25"};// 2. 映射 + 泛型:通用映射类型(复用)// 通用映射:将任意类型 T 的所有属性值类型改为 UtypeMapType<T,U>={[KinkeyofT]:U;};// 将 User 的所有属性值改为 boolean 类型typeUserToBoolean=MapType<User,boolean>;// 等价于:{ id: boolean; name: boolean; age: boolean }// 3. 映射 + 索引类型:基于原始属性值类型修改// 通用映射:将任意类型 T 的所有属性值类型改为「原始类型 + string」的联合类型typeMapWithOriginalType<T>={[KinkeyofT]:T[K]|string;// T[K] 是原始属性值类型};typeUserWithString=MapWithOriginalType<User>;// 等价于:{ id: number | string; name: string | string; age: number | string }10.2 TS 内置映射类型(常用,直接复用)
TS 内置了 6 个常用映射类型,覆盖「可选属性」「只读属性」「属性值可选」等场景,无需自定义:
| 内置映射类型 | 作用(基于 T 生成新类型) | 示例(基于 User 类型) |
|---|---|---|
Partial<T> | 将 T 的所有属性改为「可选属性」(?) | Partial<User>→ { id?: number; name?: string; age?: number } |
Required<T> | 将 T 的所有属性改为「必选属性」(移除?) | Required<Partial<User>>→ 还原 User 类型 |
Readonly<T> | 将 T 的所有属性改为「只读属性」(readonly) | Readonly<User>→ { readonly id: number; … } |
Pick<T, K> | 从 T 中「挑选部分属性 K」生成新类型(K 是 T 的属性子集) | `Pick<User, “id” |
Omit<T, K> | 从 T 中「排除部分属性 K」生成新类型(K 是 T 的属性子集) | Omit<User, "age">→ { id: number; name: string } |
Record<K, T> | 生成「属性名为 K 类型,属性值为 T 类型」的对象类型(通用对象类型) | Record<string, number>→ { [key: string]: number } |
10.3 内置映射类型实战示例
typeUser={id:number;name:string;age:number;};// 1. Partial<T>:可选属性(常用于更新对象时,只传部分属性)functionupdateUser(user:User,updates:Partial<User>):User{return{...user,...updates};// 合并原始用户和更新属性}constoldUser:User={id:1,name:"张三",age:25};// 只更新 name 属性(其他属性可选)constnewUser=updateUser(oldUser,{name:"李四"});// 2. Readonly<T>:只读属性(防止对象被修改)constreadonlyUser:Readonly<User>={id:1,name:"张三",age:25};// readonlyUser.name = "李四"; // 报错:只读属性不能修改// 3. Pick<T, K>:挑选属性(获取对象的部分属性)typeUserBasicInfo=Pick<User,"id"|"name">;// 只保留 id 和 nameconstbasicInfo:UserBasicInfo={id:1,name:"张三"};// 4. Omit<T, K>:排除属性(剔除对象的部分属性)typeUserWithoutAge=Omit<User,"age">;// 排除 age 属性constuserWithoutAge:UserWithoutAge={id:1,name:"张三"};// 5. Record<K, T>:通用对象类型(快速创建对象类型,常用)// 场景 1:键为 string,值为 User(用户列表)typeUserList=Record<string,User>;constuserList:UserList={"1":{id:1,name:"张三",age:25},"2":{id:2,name:"李四",age:30}};// 场景 2:键为联合类型,值为 string(状态描述)typeStatus="success"|"error"|"loading";typeStatusDesc=Record<Status,string>;conststatusDesc:StatusDesc={success:"请求成功",error:"请求失败",loading:"请求中"};三、TypeScript 高级特性(大型项目必备)
1. 类与 TypeScript(面向对象 + 类型约束)
TS 对 ES6 类进行了类型增强,支持「属性类型声明」「方法类型声明」「访问修饰符」「抽象类」等,让类的结构更清晰,类型更安全。
1.1 类的基础类型声明
classPerson{// 1. 属性类型声明(必选,TS 要求显式声明属性类型)name:string;// 实例属性(默认 public)age:number;readonlygender:string;// 只读属性(初始化后不能修改)// 2. 构造函数(初始化属性,参数可声明类型)constructor(name:string,age:number,gender:string){this.name=name;this.age=age;this.gender=gender;}// 3. 方法类型声明(参数类型 + 返回值类型)sayHello():string{return`Hello, 我是${this.name},${this.age}岁`;}// 方法参数类型约束setAge(newAge:number):void{if(newAge>0&&newAge<150){this.age=newAge;}}}// 实例化类(参数类型必须匹配构造函数)constperson=newPerson("张三",25,"男");console.log(person.sayHello());// Hello, 我是 张三,25 岁person.setAge(26);// 合法// person.gender = "女"; // 报错:只读属性不能修改// person.setAge(200); // 逻辑上不合法(无编译报错,需自己写判断)1.2 访问修饰符(控制属性/方法的访问权限)
TS 提供 3 种访问修饰符,用于控制类的属性和方法在「类内部、子类、外部」的访问权限:
| 修饰符 | 访问范围(类内部/子类/外部) | 说明 |
|---|---|---|
public | 全范围可访问(默认) | 类内部、子类、外部都能访问 |
private | 仅类内部可访问 | 子类和外部都不能访问 |
protected | 类内部 + 子类可访问 | 外部不能访问,子类可以继承访问 |
classParent{publicpublicProp:string="public";// 公开属性privateprivateProp:string="private";// 私有属性protectedprotectedProp:string="protected";// 受保护属性publicpublicMethod(){// 类内部可访问所有属性console.log(this.publicProp,this.privateProp,this.protectedProp);}privateprivateMethod(){console.log("私有方法");}}// 外部访问 Parent 实例constparent=newParent();parent.publicProp;// 合法(公开属性)parent.publicMethod();// 合法(公开方法)// parent.privateProp; // 报错:私有属性,外部不能访问// parent.privateMethod(); // 报错:私有方法,外部不能访问// 子类继承 ParentclassChildextendsParent{childMethod(){// 子类可访问 public 和 protected 属性,不能访问 private 属性console.log(this.publicProp);// 合法console.log(this.protectedProp);// 合法// console.log(this.privateProp); // 报错:私有属性,子类不能访问this.publicMethod();// 合法// this.privateMethod(); // 报错:私有方法,子类不能访问}}constchild=newChild();child.publicProp;// 合法// child.protectedProp; // 报错:受保护属性,外部不能访问child.childMethod();// 合法1.3 抽象类(abstract,不能实例化,只能继承)
抽象类是「类的模板」,用abstract关键字声明,不能直接实例化,只能被子类继承,用于约束子类必须实现特定的方法(类似接口,但可包含具体实现):
// 抽象类(不能实例化)abstractclassAnimal{name:string;constructor(name:string){this.name=name;}// 普通方法(有具体实现,子类可继承)eat():void{console.log(`${this.name}在吃东西`);}// 抽象方法(无具体实现,用 abstract 声明,子类必须实现)abstractmakeSound():void;}// 子类继承抽象类(必须实现抽象方法 makeSound)classDogextendsAnimal{// 实现抽象方法(方法名、参数、返回值必须与抽象方法一致)makeSound():void{console.log(`${this.name}汪汪叫`);}}classCatextendsAnimal{// 实现抽象方法makeSound():void{console.log(`${this.name}喵喵叫`);}}// 合法:实例化子类constdog=newDog("旺财");dog.eat();// 旺财 在吃东西dog.makeSound();// 旺财 汪汪叫constcat=newCat("咪宝");cat.eat();// 咪宝 在吃东西cat.makeSound();// 咪宝 喵喵叫// 报错:抽象类不能实例化// const animal = new Animal("动物");2. 模块与 TypeScript(工程化模块管理)
TS 支持 ES 模块规范(import/export)和 CommonJS 规范(require/module.exports),项目中优先用 ES 模块,TS 会自动处理模块类型推导和兼容性。
2.1 基础模块导出与导入
// src/utils.ts(模块文件)// 1. 单个导出(命名导出)exportconstPI:number=3.1415;exportfunctionadd(a:number,b:number):number{returna+b;}// 2. 多个导出(批量命名导出)constsubtract=(a:number,b:number):number=>a-b;constmultiply=(a:number,b:number):number=>a*b;export{subtract,multiply};// 3. 默认导出(一个模块只能有一个默认导出)exportdefaultclassCalculator{divide(a:number,b:number):number{if(b===0)thrownewError("除数不能为 0");returna/b;}}// src/index.ts(导入模块)// 1. 导入命名导出(必须与导出名称一致,可重命名)import{PI,add,subtractassub}from"./utils";console.log(PI);// 3.1415console.log(add(1,2));// 3console.log(sub(5,3));// 2// 2. 导入默认导出(名称可自定义)importCalcfrom"./utils";constcalc=newCalc();console.log(calc.divide(6,2));// 3// 3. 导入所有导出(用 * 接收,重命名为 utils)import*asutilsfrom"./utils";console.log(utils.PI);// 3.1415console.log(utils.multiply(2,3));// 62.2 模块类型声明(第三方模块无 TS 类型时)
若第三方模块(如某些老的 JS 库)没有提供 TS 类型定义文件(.d.ts),TS 会报错「找不到模块的类型声明」,此时需要手动编写「模块类型声明文件」:
步骤 1:新建类型声明文件src/types/index.d.ts
// 声明第三方模块 "xxx-js-lib" 的类型(假设模块导出一个函数)declaremodule"xxx-js-lib"{// 声明模块导出的函数类型exportfunctionxxxFn(param:string):void;// 声明模块导出的对象类型exportconstxxxObj:{name:string;version:string;};}步骤 2:在tsconfig.json中配置类型声明文件路径
{"compilerOptions":{"typeRoots":["./node_modules/@types","./src/types"]// 包含自定义类型声明目录}}步骤 3:正常导入第三方模块(TS 不再报错)
import{xxxFn,xxxObj}from"xxx-js-lib";xxxFn("test");// 合法,TS 识别类型console.log(xxxObj.name);// 合法3. 声明文件(.d.ts,描述 JS 代码的类型)
声明文件(后缀.d.ts)的核心作用是「给 JS 代码提供 TS 类型描述」,让 TS 能够识别 JS 代码的类型,支持以下场景:
- 第三方 JS 库无 TS 类型(如老项目、自定义 JS 工具库);
- 项目中混合 JS 和 TS,给 JS 文件提供类型;
- 全局变量/全局函数的类型声明;
3.1 声明文件基础语法
// src/types/global.d.ts(全局声明文件)// 1. 全局变量声明declareconstVERSION:string;// 全局变量 VERSION,类型为 string// 2. 全局函数声明declarefunctionlogMsg(msg:string):void;// 全局函数 logMsg// 3. 全局接口声明(可被全局使用)interfaceGlobalUser{id:number;name:string;}// 4. 模块声明(给 JS 模块提供类型)declaremodule"./utils.js"{exportfunctionformatTime(time:number):string;exportconstnowTime:number;}// src/index.ts(使用全局声明)console.log(VERSION);// 合法(全局变量,TS 识别类型)logMsg("全局函数调用");// 合法constuser:GlobalUser={id:1,name:"张三"};// 合法(全局接口)import{formatTime}from"./utils.js";// 合法(JS 模块有类型声明)formatTime(Date.now());3.2 常用声明文件场景
场景 1:全局变量声明(如 HTML 中引入的全局 JS 变量)
// 声明 HTML 中通过 <script> 引入的全局变量 jQuerydeclarevarjQuery:(selector:string)=>any;// 使用时 TS 不报错jQuery("#app").html("Hello TypeScript");场景 2:扩展已有类型(如给 String 原型添加方法)
// 扩展 String 原型,添加 reverse 方法declareinterfaceString{reverse():string;}// JS 中实现方法(src/utils.js)String.prototype.reverse=function(){returnthis.split("").reverse().join("");};// TS 中使用(无报错,识别类型)conststr="hello";console.log(str.reverse());// "olleh"4. 高级泛型技巧(大型项目实战)
4.1 泛型工具类型(基于泛型的通用类型工具)
除了 TS 内置的映射类型,还有 4 个常用的泛型工具类型,用于「类型转换」和「类型提取」,覆盖函数返回值、构造函数参数等高频场景:
| 工具类型 | 作用(基于 T 生成新类型) | 示例 |
|---|---|---|
Exclude<T, U> | 从 T 中排除「能赋值给 U」的类型 | `Exclude<“a” |
Extract<T, U> | 从 T 中提取「能赋值给 U」的类型 | `Extract<“a” |
ReturnType<T> | 提取函数 T 的返回值类型 | ReturnType<(a: number)=>string>→ string |
ConstructorParameters<T> | 提取构造函数 T 的参数类型(返回元组) | ConstructorParameters<DateConstructor>→ [number |
实战示例
// 1. Exclude<T, U>:排除类型typeAllKeys="id"|"name"|"age"|"gender";typeExcludeKeys=Exclude<AllKeys,"age"|"gender">;// 排除 age 和 gender → "id" | "name"// 2. Extract<T, U>:提取类型typeNeedKeys="name"|"address";typeExtractKeys=Extract<AllKeys,NeedKeys>;// 从 AllKeys 提取 NeedKeys 中的类型 → "name"// 3. ReturnType<T>:提取函数返回值类型(高频场景)// 定义函数functiongetUser():{id:number;name:string}{return{id:1,name:"张三"};}// 提取返回值类型 → { id: number; name: string }typeUserRes=ReturnType<typeofgetUser>;constuser:UserRes={id:2,name:"李四"};// 合法// 4. ConstructorParameters<T>:提取构造函数参数类型// Date 构造函数参数类型:number | string | Date | nulltypeDateParams=ConstructorParameters<DateConstructor>;constparams1:DateParams=[1718000000000];// 合法(时间戳)constparams2:DateParams=["2024-06-10"];// 合法(字符串日期)// 5. 自定义泛型工具类型(组合使用)// 需求:提取对象类型 T 中,值为 U 类型的属性名typePickKeysByValueType<T,U>={[KinkeyofT]:T[K]extendsU?K:never;}[keyofT];// 先映射,再提取非 never 的属性名typeUser={id:number;name:string;age:number;isActive:boolean};// 提取 User 中值为 number 类型的属性名 → "id" | "age"typeNumKeys=PickKeysByValueType<User,number>;// 提取 User 中值为 boolean 类型的属性名 → "isActive"typeBoolKeys=PickKeysByValueType<User,boolean>;4.2 泛型条件类型(T extends U ? X : Y,动态判断类型)
泛型条件类型是「基于泛型参数的条件判断,动态返回不同类型」,语法类似三元运算符,核心是「类型层面的条件分支」,常用于复杂类型的动态推导:
// 1. 基础泛型条件类型// 逻辑:若 T 是 U 的子类型,返回 X 类型,否则返回 Y 类型typeConditionalType<T,U,X,Y>=TextendsU?X:Y;// 示例:判断 T 是否为 string 类型,是则返回 number,否则返回 booleantypeStrToNum<T>=Textendsstring?number:boolean;typeA=StrToNum<string>;// number(T 是 string,返回 X)typeB=StrToNum<number>;// boolean(T 不是 string,返回 Y)typeC=StrToNum<string|number>;// number | boolean(联合类型会自动分发,分别判断)// 2. 泛型条件类型 + infer(提取类型,关键技巧)// infer 关键字:在条件类型中「提取未知类型」,类似变量声明,只能在 extends 右侧使用// 需求:提取数组的元素类型(等价于 TS 内置的 Array<T>[number])typeArrayItem<T>=TextendsArray<inferU>?U:T;typeArr1=ArrayItem<number[]>;// number(T 是数组,提取元素类型 U=number)typeArr2=ArrayItem<string[]>;// stringtypeArr3=ArrayItem<{id:number}>;// { id: number }(T 不是数组,返回自身)// 3. 实战场景:提取函数的第一个参数类型typeFirstParam<T>=Textends(first:inferU,...rest:any[])?U:never;// 函数:(a: number, b: string) => voidtypeFn1=(a:number,b:string)=>void;typeParam1=FirstParam<Fn1>;// number(提取第一个参数类型 U=number)// 函数:(name: string) => stringtypeFn2=(name:string)=>string;typeParam2=FirstParam<Fn2>;// string// 4. 嵌套泛型条件类型(复杂逻辑)// 需求:若 T 是数组,返回数组元素的返回值类型;若 T 是函数,返回函数返回值类型;否则返回 TtypeExtractReturnType<T>=TextendsArray<inferU>?(Uextends(...args:any[])=>inferR?R:U):Textends(...args:any[])=>inferR?R:T;// 场景 1:T 是函数数组 → 提取函数返回值类型typeFnArr=((a:number)=>string)[];typeRes1=ExtractReturnType<FnArr>;// string(数组元素是函数,返回函数返回值)// 场景 2:T 是普通函数 → 提取函数返回值类型typeFn=(x:string)=>boolean;typeRes2=ExtractReturnType<Fn>;// boolean// 场景 3:T 是普通数组 → 返回数组元素类型typeNumArr=number[];typeRes3=ExtractReturnType<NumArr>;// number// 场景 4:T 是普通对象 → 返回自身typeObj={name:string};typeRes4=ExtractReturnType<Obj>;// { name: string }5. 枚举(Enum,enum关键字,定义命名常量集合)
枚举是 TS 新增的「命名常量集合」,用于定义一组固定的、有语义的常量(如状态码、性别、角色等),核心价值是「代码可读性提升」和「避免魔法值」,支持数字枚举和字符串枚举。
5.1 数字枚举(默认,值为数字,支持自增)
// 1. 基础数字枚举(未指定值时,从 0 开始自增)enumStatusCode{Success,// 0(默认值)Error,// 1(自增 1)Loading// 2(自增 1)}// 使用枚举(通过「枚举名.常量名」访问,值为数字)console.log(StatusCode.Success);// 0console.log(StatusCode.Error);// 1// 反向映射:通过值获取枚举名(数字枚举独有)console.log(StatusCode[0]);// "Success"// 2. 指定初始值的数字枚举(从指定值开始自增)enumRole{Admin=100,// 初始值 100User=200,// 200(手动指定,不影响后续自增)Guest// 201(自增 1)}console.log(Role.Admin);// 100console.log(Role.Guest);// 201// 3. 实战场景:接口请求状态码enumHttpCode{OK=200,BadRequest=400,Unauthorized=401,Forbidden=403,NotFound=404,ServerError=500}functionhandleResponse(code:HttpCode){switch(code){caseHttpCode.OK:console.log("请求成功");break;caseHttpCode.NotFound:console.log("资源不存在");break;caseHttpCode.ServerError:console.log("服务器错误");break;}}handleResponse(HttpCode.OK);// 请求成功5.2 字符串枚举(值为字符串,无自增,需手动指定)
字符串枚举的每个常量必须手动指定字符串值,不支持反向映射,可读性更强,常用于描述性的常量:
// 1. 基础字符串枚举enumGender{Male="MALE",Female="FEMALE",Other="OTHER"}// 使用枚举console.log(Gender.Male);// "MALE"// 字符串枚举无反向映射,以下代码报错// console.log(Gender["MALE"]);// 2. 实战场景:用户状态描述enumUserStatus{Active="已激活",Inactive="未激活",Frozen="已冻结",Deleted="已删除"}constuser={id:1,name:"张三",status:UserStatus.Active// 语义清晰,避免直接写字符串 "已激活"};if(user.status===UserStatus.Frozen){console.log("用户账号已冻结");}5.3 枚举注意事项
- 枚举会被编译为 JS 代码(不是纯类型,会增加代码体积),编译后是对象;
- 数字枚举支持反向映射,字符串枚举不支持;
- 枚举常量值一旦定义,不能修改(本质是常量);
- 若只需「类型约束」,无需运行时常量,可改用「字面量联合类型」(更轻量):
// 字面量联合类型(替代字符串枚举,纯类型,无 JS 代码)typeGender="MALE"|"FEMALE"|"OTHER";constgender:Gender="MALE";// 效果与字符串枚举一致,更轻量
6. 高级类型收窄(精准缩小类型范围)
除了基础类型守卫,TS 还支持多种高级类型收窄方式,进一步提升类型判断的精准度,避免冗余代码:
6.1 真值收窄(if (val)排除 falsy 值)
通过「真值判断」自动排除null、undefined、0、""、false等 falsy 类型:
functionprintMsg(msg:string|null|undefined){// 真值判断:排除 null 和 undefined(TS 自动收窄类型为 string)if(msg){console.log(msg.length);// 合法,msg 是 string 类型}else{console.log("msg 为空");// msg 是 null 或 undefined}}printMsg("hello");// 打印 5printMsg(null);// 打印 msg 为空6.2 相等收窄(===/!==判断类型)
通过「严格相等判断」精准收窄联合类型中的某个类型:
typeDirection="up"|"down"|"left"|"right";typePosition={x:number;y:number};functionmove(dir:Direction,pos:Position):Position{if(dir==="up"){// dir 收窄为 "up" 类型return{...pos,y:pos.y-1};}elseif(dir==="down"){// dir 收窄为 "down" 类型return{...pos,y:pos.y+1};}elseif(dir==="left"){return{...pos,x:pos.x-1};}else{// dir 收窄为 "right" 类型(TS 自动推导剩余类型)return{...pos,x:pos.x+1};}}6.3 类型谓词进阶(复杂类型收窄)
结合「泛型 + 类型谓词」,实现通用的复杂类型收窄:
// 通用类型守卫:判断 val 是否为数组,且数组元素是 T 类型functionisArrayOf<T>(val:any,predicate:(item:any)=>itemisT):valisT[]{returnArray.isArray(val)&&val.every(predicate);}// 判断是否为 number 类型functionisNumber(val:any):valisnumber{returntypeofval==="number";}// 判断是否为 string 类型functionisString(val:any):valisstring{returntypeofval==="string";}// 使用通用类型守卫constarr1=[1,2,3];if(isArrayOf(arr1,isNumber)){console.log(arr1.reduce((a,b)=>a+b));// 合法,arr1 是 number[]}constarr2=["a","b",3];if(isArrayOf(arr2,isString)){console.log(arr2.join(","));}else{console.log("数组包含非字符串元素");// 执行此处,arr2 有 number 元素}四、TypeScript 配置文件详解(tsconfig.json)
tsconfig.json是 TS 项目的核心配置文件,控制 TS 的编译规则、类型校验严格度、输出目录等,强烈推荐开启严格模式(strict: true),避免类型逃逸,以下是关键配置项详解:
1. 核心编译选项(compilerOptions)
{"compilerOptions":{// 1. 基础配置"target":"ES2020",// 编译后 JS 版本(ES3/ES5/ES6/ES2020+,推荐 ES2020+)"module":"ESNext",// 模块规范(CommonJS/ESNext/AMD,前端推荐 ESNext)"outDir":"./dist",// 编译后 JS 文件输出目录(避免源码和编译文件混合)"rootDir":"./src",// TS 源码目录(指定编译范围的根目录)"lib":["ES2020","DOM"],// 编译时依赖的库(前端需加 DOM,Node.js 不加)// 2. 严格模式(核心,必须开启)"strict":true,// 开启所有严格类型校验(等价于下面 7 个严格选项全为 true)"noImplicitAny":true,// 禁止隐式 any 类型(变量未指定类型且无法推导时报错)"strictNullChecks":true,// 严格 null 检查(null/undefined 不能赋值给其他类型)"strictFunctionTypes":true,// 严格函数类型检查(避免函数参数类型兼容问题)"strictPropertyInitialization":true,// 严格属性初始化(类属性必须初始化或在构造函数赋值)// 3. 模块与路径配置"moduleResolution":"Bundler",// 模块解析策略(Bundler 适配 Vite/Rollup,Node 适配 Node.js)"baseUrl":"./",// 基础路径(用于路径别名解析)"paths":{// 路径别名(简化导入路径,如 @ 指向 src)"@/*":["src/*"]},"esModuleInterop":true,// 兼容 CommonJS 和 ESM 模块(避免导入 CommonJS 模块报错)"allowSyntheticDefaultImports":true,// 允许默认导入无默认导出的模块// 4. 类型检查与报错配置"noUnusedParameters":true,// 禁止未使用的函数参数(提示冗余参数)"noUnusedLocals":true,// 禁止未使用的局部变量(提示冗余变量)"noImplicitReturns":true,// 禁止函数隐式返回(分支必须有 return 或无返回值)"skipLibCheck":true,// 跳过第三方库的类型检查(提升编译速度,避免库类型冲突)// 5. 输出与调试配置"sourceMap":true,// 生成 SourceMap 文件(调试时关联 TS 源码,开发环境开启,生产可关闭)"declaration":false,// 是否生成类型声明文件 .d.ts(库开发开启,项目开发关闭)"removeComments":true,// 编译时移除注释(生产环境开启,减少代码体积)"minify":false// 是否压缩编译后的 JS(项目开发关闭,生产用打包工具压缩)},// 2. 编译范围配置"include":["./src/**/*"],// 需要编译的文件(src 目录下所有文件,递归匹配)"exclude":["node_modules","./src/**/*.test.ts"],// 排除的文件(node_modules、测试文件)"files":["./src/index.ts"],// 指定编译的单个文件(与 include 互斥,优先 include)// 3. 继承配置(复用其他配置文件,如 tsconfig.base.json)"extends":"./tsconfig.base.json"}2. 不同场景配置示例
场景 1:前端项目(Vite + Vue3/React + TS)
{"compilerOptions":{"target":"ES2020","module":"ESNext","outDir":"./dist","rootDir":"./src","lib":["ES2020","DOM","DOM.Iterable"],"strict":true,"moduleResolution":"Bundler","baseUrl":"./","paths":{"@/*":["src/*"]},"esModuleInterop":true,"allowSyntheticDefaultImports":true,"sourceMap":true,"skipLibCheck":true,"noUnusedParameters":true,"noUnusedLocals":true},"include":["src/**/*"],"exclude":["node_modules","src/**/*.test.ts"]}场景 2:Node.js 项目(Express + TS)
{"compilerOptions":{"target":"ES2020","module":"CommonJS",// Node.js 用 CommonJS 模块"outDir":"./dist","rootDir":"./src","lib":["ES2020"],// Node.js 无需 DOM 库"strict":true,"moduleResolution":"Node",// 用 Node 模块解析策略"baseUrl":"./","paths":{"@/*":["src/*"]},"esModuleInterop":true,"sourceMap":true,"skipLibCheck":true},"include":["src/**/*"],"exclude":["node_modules"]}五、TypeScript 实战场景(框架集成 + 项目技巧)
1. Vue3 + TypeScript 集成(最常用前端场景)
Vue3 原生支持 TS,配合@vitejs/plugin-vue插件可快速搭建 TS 项目,核心是「组件 props 类型约束」「响应式数据类型」「生命周期方法类型」。
1.1 组件 Props 类型约束(3 种方式)
<!-- src/components/HelloWorld.vue --> <template> <div>{{ msg }} - {{ user.name }}</div> </template> <script setup lang="ts"> // 方式 1:defineProps + 接口(推荐,结构清晰) interface User { id: number; name: string; age?: number; } interface Props { msg: string; // 必选 prop user: User; // 复杂类型 prop count?: number; // 可选 prop } const props = defineProps<Props>(); // 方式 2:defineProps + 类型别名 // type Props = { // msg: string; // user: { id: number; name: string }; // }; // const props = defineProps<Props>(); // 方式 3:defineProps 直接声明(简单场景) // const props = defineProps<{ // msg: string; // user: { id: number; name: string }; // }>(); // 访问 props,TS 自动识别类型 console.log(props.msg.length); // msg 是 string 类型 console.log(props.user.id); // user 是 User 类型 </script>1.2 响应式数据类型(ref/reactive)
<script setup lang="ts"> import { ref, reactive, Ref } from 'vue'; // 1. ref 类型(基础类型,TS 自动推导) const count = ref(0); // 自动推导为 Ref<number> count.value = 1; // 正确,value 是 number 类型 // count.value = "1"; // 报错 // 2. ref 显式声明类型(复杂场景,如联合类型) const status: Ref<"success" | "error" | "loading"> = ref("loading"); status.value = "success"; // 合法 // 3. reactive 类型(对象类型,TS 自动推导) const user = reactive({ id: 1, name: "张三", age: 25 }); user.age = 26; // 合法 // user.gender = "男"; // 报错:user 无 gender 属性 // 4. reactive 显式声明类型(接口/类型别名) interface User { id: number; name: string; hobbies?: string[]; } const user2 = reactive<User>({ id: 2, name: "李四", hobbies: ["打球", "看书"] }); </script>1.3 事件处理类型
<template> <button @click="handleClick">点击</button> <input @input="handleInput" /> </template> <script setup lang="ts"> // 1. 点击事件(MouseEvent 类型) const handleClick = (e: MouseEvent) => { console.log(e.target); // 正确,e 是 MouseEvent 类型 }; // 2. 输入事件(InputEvent 类型) const handleInput = (e: InputEvent) => { // 类型守卫:判断 target 是 HTMLInputElement if (e.target instanceof HTMLInputElement) { console.log(e.target.value); // 合法,获取输入框值 } }; // 3. 自定义事件(defineEmits 声明类型) const emit = defineEmits<{ (e: 'change', value: number): void; // 事件名 change,参数类型 number (e: 'update', user: { id: number; name: string }): void; // 事件名 update,参数类型 User }>(); // 触发自定义事件(参数类型必须匹配) emit('change', 100); emit('update', { id: 1, name: "张三" }); </script>2. React + TypeScript 集成
React 与 TS 集成核心是「组件 props 类型」「函数组件类型」「Hooks 类型」,配合@types/react类型库使用。
2.1 函数组件 Props 类型
// src/components/Hello.tsx import React from 'react'; // 1. 接口声明 Props 类型(推荐) interface HelloProps { name: string; age?: number; // 可选 prop onClick: (msg: string) => void; // 函数类型 prop } // 函数组件:参数 props 类型为 HelloProps,返回值为 React.ReactElement const Hello: React.FC<HelloProps> = (props) => { const { name, age = 18, onClick } = props; return ( <div> <h1>Hello {name}, {age} 岁</h1> <button onClick={() => onClick(`Hello ${name}`)}>点击</button> </div> ); }; export default Hello; // 使用组件(参数类型必须匹配) // src/App.tsx import Hello from './components/Hello'; const App = () => { const handleClick = (msg: string) => { console.log(msg); }; return ( <div> <Hello name="张三" age={25} onClick={handleClick} /> </div> ); }; export default App;2.2 Hooks 类型约束
import React, { useState, useEffect, useRef, useCallback } from 'react'; interface User { id: number; name: string; } const UserComponent = () => { // 1. useState 类型(自动推导或显式声明) const [count, setCount] = useState(0); // 自动推导 number 类型 const [user, setUser] = useState<User | null>(null); // 显式声明联合类型 // 2. useRef 类型 const inputRef = useRef<HTMLInputElement>(null); // 输入框 DOM 类型 const timerRef = useRef<NodeJS.Timeout | null>(null); // 定时器类型 // 3. useEffect 类型(无返回值或返回清理函数) useEffect(() => { timerRef.current = setInterval(() => { setCount(prev => prev + 1); }, 1000); // 清理函数(无返回值类型) return () => { if (timerRef.current) clearInterval(timerRef.current); }; }, []); // 4. useCallback 类型(指定参数和返回值类型) const handleUpdateUser = useCallback((newUser: User) => { setUser(newUser); }, []); // 5. 事件处理类型 const handleFocus = () => { inputRef.current?.focus(); // 可选链操作,避免 null 报错 }; return ( <div> <input ref={inputRef} type="text" /> <button onClick={handleFocus}>聚焦输入框</button> <p>计数:{count}</p> <button onClick={() => handleUpdateUser({ id: 1, name: "张三" })}>更新用户</button> </div> ); }; export default UserComponent;3. Node.js + TypeScript 集成(Express 示例)
Node.js 项目用 TS 需配合ts-node(直接运行 TS 文件)和@types/node(Node.js 类型声明),核心是「路由参数类型」「请求响应类型」。
3.1 项目初始化与配置
- 安装依赖:
npminstallexpressnpminstalltypescript ts-node @types/node @types/express -D- 生成
tsconfig.json:
{"compilerOptions":{"target":"ES2020","module":"CommonJS","outDir":"./dist","rootDir":"./src","strict":true,"moduleResolution":"Node","esModuleInterop":true,"sourceMap":true},"include":["src/**/*"],"exclude":["node_modules"]}- package.json 脚本:
{"scripts":{"dev":"ts-node src/index.ts",// 开发环境运行"build":"tsc",// 编译 TS 为 JS"start":"node dist/index.js"// 生产环境运行}}3.2 核心代码示例(Express 路由 + TS 类型)
// src/index.tsimportexpress,{Express,Request,Response,NextFunction}from'express';constapp:Express=express();constport=3000;// 中间件:解析 JSON 请求体app.use(express.json());// 1. 接口请求/响应类型约束(Request/Response 泛型)interfaceUser{id:number;name:string;age:number;}// GET 请求:获取用户列表(Response 泛型指定返回值类型)app.get('/api/users',(req:Request,res:Response<User[]>)=>{constusers:User[]=[{id:1,name:"张三",age:25},{id:2,name:"李四",age:30}];res.status(200).json(users);});// 2. 路径参数类型(Request 第一个泛型指定 params 类型)app.get('/api/users/:id',(req:Request<{id:string}>,res:Response<User>)=>{constid=parseInt(req.params.id);// params.id 是 string 类型,需转为 numberconstuser:User={id,name:"张三",age:25};res.status(200).json(user);});// 3. 请求体类型(Request 第二个泛型指定 body 类型)app.post('/api/users',(req:Request<{},{},User>,res:Response<User>)=>{constnewUser:User={...req.body};res.status(201).json(newUser);});// 4. 错误处理中间件(NextFunction 类型)app.use((err:Error,req:Request,res:Response,next:NextFunction)=>{console.error(err.stack);res.status(500).json({message:err.message});});// 启动服务app.listen(port,()=>{console.log(`Server running at http://localhost:${port}`);});4. TypeScript 项目最佳实践
4.1 类型约束原则
- 禁用
any类型:any会关闭类型校验,导致 TS 失去意义,可用unknown替代(需类型守卫); - 优先类型推导:变量声明时直接赋值,让 TS 自动推导类型,减少冗余代码;
- 精准类型定义:避免过度使用
object、unknown,尽量用接口/类型别名定义具体结构; - 开启严格模式:
strict: true强制严格类型校验,提前发现潜在 bug。
4.2 性能优化技巧
- 跳过第三方库类型检查:
skipLibCheck: true,避免node_modules中库的类型冲突,提升编译速度; - 合理使用
exclude:排除测试文件、构建产物目录,减少编译范围; - 生产环境关闭 SourceMap:
sourceMap: false,减少代码体积; - 使用
tsc --incremental:增量编译,只编译修改的文件,提升大型项目编译速度。
4.3 代码规范建议
- 类型命名:接口/类型别名用 PascalCase(大驼峰),如
User、UserProps; - 泛型参数:简单泛型用
T、U,复杂泛型用语义化名称,如User、Props; - 可选属性:非必选的属性统一用
?标记,避免undefined联合类型冗余; - 类型复用:重复使用的类型抽离为接口/类型别名,避免重复定义;
- 注释规范:复杂类型添加 JSDoc 注释,说明类型含义,提升可读性:
/** * 用户信息类型 * @property id - 用户唯一标识 * @property name - 用户名(必选) * @property age - 年龄(可选,默认 18) */interfaceUser{id:number;name:string;age?:number;}
六、TypeScript 常见问题与解决方案
1. 类型“null”不能赋值给类型“string”(严格 null 检查)
原因:开启strictNullChecks: true后,null/undefined不能赋值给其他类型;
解决方案:用联合类型声明,或用可选链/非空断言排除 null:
// 方案 1:联合类型声明(推荐)letmsg:string|null=null;msg="hello";// 合法// 方案 2:非空断言(!,确定值不为 null/undefined 时使用)constinput=document.getElementById("input")!;// ! 断言 input 不为 nullinput.value="test";// 方案 3:可选链(?.,值为 null/undefined 时短路,返回 undefined)constuser:{name?:string}={};console.log(user.name?.length);// 不会报错,返回 undefined2. 找不到模块“xxx”的声明文件(第三方库无 TS 类型)
原因:第三方 JS 库未提供.d.ts类型声明文件;
解决方案:
- 安装社区类型声明(优先):
npm install @types/xxx -D(如@types/express); - 手动编写类型声明(无社区类型时):在
src/types新建.d.ts文件,声明模块; - 临时忽略(不推荐):
// @ts-ignore注释跳过该行为的类型检查。
3. 函数参数“xxx”没有初始化,且没有指定类型
原因:开启strictPropertyInitialization: true后,类属性必须初始化;
解决方案:
- 直接初始化属性;
- 在构造函数中赋值;
- 用
!断言属性会被初始化(确定会赋值时):
classPerson{name:string;// 报错:未初始化age:number=18;// 方案 1:直接初始化gender!:string;// 方案 3:! 断言会初始化constructor(name:string){this.name=name;// 方案 2:构造函数赋值}}4. 类型“string”不能赋值给类型““success” | “error””(字面量联合类型)
原因:变量是普通 string 类型,无法赋值给固定值的字面量联合类型;
解决方案:用类型断言或类型守卫缩小类型:
typeStatus="success"|"error";// 方案 1:类型断言(确定值在联合类型中)conststatus1:Status="success"asStatus;// 方案 2:类型守卫(动态判断值)functionisStatus(val:string):valisStatus{return["success","error"].includes(val);}conststatusStr="success";if(isStatus(statusStr)){conststatus2:Status=statusStr;// 合法}七、总结
TypeScript 作为 JS 的强类型超集,核心价值是「提前发现错误、提升开发效率、支撑大型工程」,从基础类型到高级泛型,从框架集成到项目实战,掌握后能显著提升代码质量和开发体验。
核心学习路径
- 基础阶段:掌握基础类型、数组、元组、函数类型,能编写简单 TS 代码;
- 进阶阶段:吃透联合/交叉类型、接口/类型别名、泛型、类型守卫,应对复杂类型场景;
- 高级阶段:熟练使用映射类型、泛型工具类型、枚举、声明文件,适配框架和工程化需求;
- 实战阶段:结合 Vue3/React/Node.js 项目,落地 TS 最佳实践,解决实际问题。
学习建议
- 边学边练:TS 是实践型语言,结合小案例练习,避免只看理论;
- 循序渐进:先掌握基础类型和接口,再攻克泛型(难点),逐步深入;
- 查阅文档:TS 官方文档(TypeScript 中文网)是最佳学习资料,遇到问题优先查文档;
- 项目落地:在实际项目中使用 TS,从简单场景开始,逐步覆盖全项目,积累实战经验。
随着前端工程化的发展,TypeScript 已成为大型项目的标配,掌握 TS 不仅能提升个人竞争力,还能为后续学习框架源码、搭建工程化体系打下坚实基础,建议持续深入学习和实践!