两小时带你入门 TypeScript(下):工程配置、类型报错与高级类型 在前两篇中我们已经掌握了 TypeScript 的基础语法、泛型以及 React、Vue 项目中的常见使用方式。本篇作为系列的最后一篇主要解决三个问题如何处理 TypeScript 常见报错、如何在实际项目中配置 TS以及如何理解keyof、映射类型、条件类型和infer等高级类型。一、DOM 类型与类型断言在 TypeScript 中DOM 操作也受到类型系统的约束。例如const input document.getElementById(username) console.log(input.value)这段代码会报错因为getElementById的返回类型是HTMLElement | null。TypeScript 无法确定元素是否存在也无法确定它一定是具有value属性的输入框。可以使用泛型指定元素类型const input document.querySelectorHTMLInputElement( #username ) console.log(input?.value)这里的HTMLInputElement是浏览器提供的输入框元素类型而?.用于处理元素可能为null的情况。1.1 类型断言 as如果开发者明确知道某个值的具体类型可以使用asconst input document.getElementById( username ) as HTMLInputElement | nullas的作用是告诉 TypeScript 应该怎样理解这个值而不是转换数据。例如const value 123 as string这并不会执行任何类型转换。此外TypeScript 还提供非空断言!const input document.querySelector(#username)!它表示忽略null和undefined的可能性。但需要注意类型断言和非空断言都不会在运行时验证数据。如果判断错误代码仍然可能报错因此应该优先使用类型收窄和空值判断。二、TypeScript 常见报错TypeScript 初学阶段遇到的错误主要集中在类型不匹配、空值以及类型不明确这几个方面。2.1 类型不匹配let age: number 18 age 20 // 报错这是因为string不能赋值给number。如果确实需要将字符串转换为数字应该使用 JavaScript 的类型转换方法age Number(20)不要习惯性地通过as强行绕过类型检查。2.2 对象可能为空interface User { name: string } function printName(user?: User) { console.log(user.name) // 报错 }这里的user?表示参数可以不传因此可能是undefined。可以通过条件判断进行类型收窄function printName(user?: User) { if (!user) return console.log(user.name) }也可以使用可选链console.log(user?.name)两者的区别是条件判断可以在后续代码中排除空值而可选链允许结果为undefined。2.3 any 与 unknownany表示放弃大部分类型检查let data: any 123 data.toUpperCase() // TS 不报错运行时可能出错如果某个数据的类型确实未知更推荐使用unknownlet data: unknown hellounknown不允许直接调用某个类型特有的方法必须先检查if (typeof data string) { console.log(data.toUpperCase()) }因此unknown与any的区别在于any 允许跳过检查而 unknown 要求先确定类型再使用。实际项目中应该尽可能使用明确类型无法确定数据结构时再考虑unknown。三、tsconfig.json 与工程配置TypeScript 项目通常通过tsconfig.json管理类型检查和编译配置。初学阶段重点了解下面三个选项{ compilerOptions: { strict: true, noEmit: true, skipLibCheck: true } }strict启用一组严格类型检查规则建议新项目开启。noEmit只进行类型检查不生成 JavaScript 文件。skipLibCheck跳过对声明文件的完整类型检查减少部分依赖类型检查开销。其中strict已经包含noImplicitAny等严格检查规则通常无需重复声明。以上只是配置示例实际项目还需要 JSX、模块解析等设置建议优先采用 Vite、Vue 或 React 工程模板提供的完整配置。3.1 JavaScript 项目如何迁移到 TS已有的 JavaScript 项目不需要一次性全部改成 TypeScript可以逐步迁移。建议按照下面的顺序API 数据类型化给接口响应添加interface或type。状态类型化为useState、ref等状态添加必要的泛型。组件类型化定义 Props、事件和回调函数类型。工具函数类型化逐步迁移公共函数和业务逻辑。在迁移过程中可以通过allowJs允许 JS 与 TS 文件共存再逐步将.js改为.tsReact JSX 文件改为.tsx。另外需要区分代码转换和类型检查。Vite 等工具可以转换 TypeScript 代码但不代表一定进行了完整类型检查。可以根据项目配置使用tsc --noEmit或 Vue 项目中的vue-tsc --noEmit检查类型。四、高级类型从使用类型到操作类型前面学习的基本类型、联合类型和泛型已经能够满足大部分普通业务开发需求。但在阅读第三方库源码时我们还会遇到一些用于操作和生成类型的高级语法。4.1 keyof获取属性名类型假设存在一个用户类型interface User { id: number name: string age: number }使用keyoftype UserKeys keyof User得到的类型相当于type UserKeys id | name | age也就是说keyof可以将对象类型的属性名提取为联合类型。这在约束对象属性名时很有用function getValueT, K extends keyof T( obj: T, key: K ): T[K] { return obj[key] }这里T表示对象类型。K extends keyof T限制属性名必须存在于对象中。T[K]表示对应属性的值类型。因此调用函数时既能限制传入的属性名又能自动推断返回值类型。4.2 typeof从变量获取类型JavaScript 中的typeof用于检查值的类型而 TypeScript 还允许在类型位置使用它。const user { id: 1, name: 张三 } type UserType typeof user此时UserType就是由user对象推断出的类型type UserType { id: number name: string }这可以避免重复编写已经存在的数据结构。4.3 常见内置工具类型TypeScript 提供了一些常见的类型处理工具。假设存在interface User { id: number name: string age: number }Partial让所有属性变为可选type UpdateUser PartialUser适用于局部更新对象的场景。Pick提取部分属性type UserInfo PickUser, id | name得到的类型只包含id和name。Omit排除部分属性type CreateUser OmitUser, id得到的类型不包含id。Readonly将属性设为只读type ReadonlyUser ReadonlyUser它会将对象的顶层属性变成只读属性。这些工具类型的本质就是根据已有类型生成新的类型减少重复声明。4.4 映射类型Mapped Types假设我们希望自己实现Partialtype MyPartialT { [K in keyof T]?: T[K] }这就是映射类型。其中keyof T获取T的所有属性名。K in keyof T遍历这些属性名。T[K]获取对应属性的类型。?将属性变为可选。因此type NewUser MyPartialUser生成的类型就相当于type NewUser { id?: number name?: string age?: number }映射类型可以理解为在类型层面遍历对象属性并按照规则生成新的对象类型。4.5 条件类型Conditional Types条件类型可以理解为类型系统中的条件判断。基本语法T extends U ? X : Y表示如果T可以赋值给U就得到类型X否则得到类型Y。例如type IsStringT T extends string ? true : false使用时type A IsStringstring // true type B IsStringnumber // false这里得到的true和false是字面量类型而不是运行时的计算结果。条件类型常用于根据不同的泛型参数动态生成不同的类型结果。4.6 infer从复杂类型中提取类型infer通常与条件类型配合使用用于提取类型中的某个部分。例如我们希望得到 Promise 内部的数据类型type UnwrapPromiseT T extends Promiseinfer U ? U : T这里Promiseinfer U表示尝试匹配一个 Promise并把其中的数据类型推断为U。例如type A UnwrapPromisePromisestring // string type B UnwrapPromisePromisenumber // number当T是Promisestring时TypeScript 就能推断出U是string。因此infer 的核心作用就是在条件类型中声明一个待推断的类型变量从已有类型结构中提取需要的信息。实际开发中TypeScript 已经提供了ReturnTypeT、ParametersT、AwaitedT等工具类型很多时候可以直接使用不需要重复实现。初学阶段能够看懂infer的基本含义即可无需急于研究复杂类型体操。五、总结至此《两小时带你入门 TypeScript》系列就结束了。上篇介绍了 TypeScript 的基础类型与核心语法中篇通过泛型和 React、Vue 实战了解如何在前端项目中使用 TS下篇则补充了工程配置、常见类型报错以及一些高级类型操作。对于刚接触 TypeScript 的前端开发者现阶段最重要的不是掌握复杂的类型体操而是能够在真实项目中正确使用类型系统。建议优先完成一次小型 JS 项目向 TS 的迁移重点练习接口数据、组件 Props、状态和事件的类型声明。之后再通过阅读项目源码逐步熟悉keyof、工具类型、映射类型、条件类型和infer。TypeScript 的价值不在于让代码变复杂而在于让数据约束更加清晰、减少潜在错误并提高项目的可维护性。