TypeScript 完全指南(上):从零开始掌握类型系统
为什么说“任何 JavaScript 开发者都应该学 TypeScript”读完前三部分你就明白了。很多初学者觉得 TypeScript 只是给 JS 加了个“:string”的语法糖但实际上它带来的收益远超你的想象。本文将带你从为什么需要 TS到环境搭建再到基础类型系统一步步建立起坚实的 TS 知识体系。本文不涉及任何前端框架React/Vue 等所有示例纯 TypeScript可独立运行。第一部分为什么你需要 TypeScript1.1 一个“血泪史”案例引入想象你在开发一个电商网站的促销模块有一个函数用来计算订单总价javascript// 这是纯 JavaScript function calculateTotal(price, quantity, discount) { return price * quantity - discount; }由于 JS 是动态类型你可能会在运行时遇到各种诡异情况javascriptcalculateTotal(100, 2, 20); // 输出 100200 - 20 → 10020020 实际100200 - 20 → NaN calculateTotal(100, 2, 20); // 输出 100 * 2 - 20 → 200 - 20 180字符串 2 被隐式转换为数字 calculateTotal(100, 2, 20); // 输出 200 - 20 → 18020 被隐式转换为数字这些错误只有在代码真正运行到那一步时才会暴露。如果测试覆盖不充分它们就会悄悄溜到生产环境变成用户的差评。这就是 TypeScript 要解决的核心问题在编码阶段就捕获类型错误。1.2 TypeScript 是什么TypeScript 由微软在 2012 年推出是 JavaScript 的静态类型超集。用公式表示就是TypeScript JavaScript 类型系统 编译时检查超集所有合法的 JS 代码在 TS 中也是完全合法的。你可以把.js文件直接改名为.ts它就能被 TS 编译器接受当然类型检查可能会报警告。静态类型类型在编译时就确定而不是等到运行时。编译时检查在代码变成 JS 之前TS 编译器会扫描所有类型不一致的地方并报错。1.3 TypeScript 带来的核心价值场景纯 JavaScript加上 TypeScript函数参数传错类型运行时产生奇怪结果可能几天后才被发现编写时 IDE 立即红线报错编译不通过访问对象不存在的属性undefined后续逻辑可能崩溃类型错误无法编译重构修改函数签名全局搜索手动修改容易遗漏类型系统追踪所有调用处一键重构团队协作需要大量文档和口头沟通类型即文档新人看类型就能理解用法代码补全基础的字符串匹配精确的属性/方法提示甚至能推导出返回类型根据微软的统计数据启用严格类型检查的 TypeScript 项目运行时类型相关 bug 减少约 80%。1.4 一个直观的对比typescript// 用 TypeScript 重写上面的 calculateTotal function calculateTotal(price: number, quantity: number, discount: number): number { return price * quantity - discount; } // 下面任何一行都会导致编译错误而不是运行时崩溃 // calculateTotal(100, 2, 20); // ❌ 类型string的参数不能赋给类型number的参数 // calculateTotal(100, 2, 20); // ❌ 同上 // calculateTotal(100, 2, 20); // ❌ 同上 // calculateTotal(100, 2); // ❌ 参数不足 // 正确调用 const total calculateTotal(100, 2, 20); // ✅ 类型安全total 自动被推断为 number console.log(total); // 180现在任何试图传错类型的调用都会在编译时被拦截你不需要运行代码就能发现问题。第二部分TypeScript 环境搭建5分钟上手2.1 安装 TypeScript 编译器TypeScript 编译器tsc是一个 npm 包你可以全局安装也可以在项目中本地安装。bash# 全局安装适合快速尝试 npm install -g typescript # 验证安装成功 tsc --version # 输出类似Version 5.8.2 # 本地安装推荐用于实际项目 mkdir my-ts-project cd my-ts-project npm init -y npm install -D typescript2.2 创建你的第一个 TypeScript 文件新建一个hello.tstypescript// hello.ts function greet(name: string): string { return Hello, ${name}! Welcome to TypeScript.; } const message greet(TypeScript Beginner); console.log(message);2.3 编译并运行bash# 使用 tsc 命令编译如果全局安装 tsc hello.ts # 或者使用 npx如果本地安装 npx tsc hello.ts # 上述命令会生成一个 hello.js 文件 # 然后用 Node 运行它 node hello.js # 输出Hello, TypeScript Beginner! Welcome to TypeScript.2.4 初始化 tsconfig.json推荐对于稍大一点的项目你通常会需要一个配置文件来控制编译行为。bashtsc --init这会在当前目录生成一个tsconfig.json。它包含了大量可配置选项但现在你只需要知道有了它你就可以直接运行tsc而不指定文件名编译器会按照配置处理所有.ts文件。最简单的配置示例json{ compilerOptions: { target: ES2020, // 编译到哪个版本的 JS module: commonjs, // 模块系统 outDir: ./dist, // 输出目录 rootDir: ./src, // 源码目录 strict: true // 开启所有严格类型检查强烈推荐 } }然后在项目中创建src文件夹把.ts文件放进去运行tsc即可。2.5 使用 ts-node 直接运行开发阶段如果你不想每次都编译再运行可以使用ts-nodebashnpm install -D ts-node npx ts-node hello.ts它会直接执行.ts文件适合快速验证代码。第三部分TypeScript 基础类型系统全文重点TypeScript 继承了 JS 的所有数据类型并额外增加了类型注解和更多类型。下面逐一讲解。3.1 八大基础类型3.1.1 布尔类型booleantypescriptlet isCompleted: boolean false; let isActive: boolean true;3.1.2 数字类型number包括整数、浮点数、十六进制、二进制、八进制等。typescriptlet decimal: number 42; let hex: number 0xf00d; // 十六进制 let binary: number 0b1010; // 二进制 let octal: number 0o744; // 八进制 let float: number 3.14159;3.1.3 字符串类型string支持单引号、双引号和模板字符串。typescriptlet single: string Hello; let double: string World; let template: string Hello ${single} ${double}!;3.1.4 数组类型ArrayT或T[]typescript// 两种等价写法 let numbers1: number[] [1, 2, 3, 4]; let numbers2: Arraynumber [1, 2, 3, 4]; // 字符串数组 let fruits: string[] [apple, banana, orange]; // 混合类型数组联合类型 let mixed: (string | number)[] [1, two, 3, four];3.1.5 元组类型Tuple元组是固定长度、固定类型顺序的数组。typescript// 定义第一个元素是 string第二个是 number第三个是 boolean let person: [string, number, boolean] [Alice, 25, true]; // 错误示例 // person [25, Alice, true]; // ❌ 类型顺序错误 // person [Alice, 25]; // ❌ 缺少第三个元素 // person [Alice, 25, true, extra]; // ❌ 超出长度 // 访问元素 console.log(person[0]); // Alice console.log(person[1]); // 25元组常用于表示结构化数据比如 CSV 的一行、坐标等。3.1.6 枚举类型enum枚举为一组数值或字符串常量赋予有意义的名称。数字枚举默认从 0 开始typescriptenum Direction { Up, // 0 Down, // 1 Left, // 2 Right // 3 } let move: Direction Direction.Up; console.log(move); // 0 // 手动指定值 enum HttpStatus { OK 200, NotFound 404, InternalError 500 } console.log(HttpStatus.OK); // 200 console.log(HttpStatus[404]); // NotFound反向映射字符串枚举更易读typescriptenum LogLevel { INFO INFO, WARN WARN, ERROR ERROR } let level: LogLevel LogLevel.INFO; console.log(level); // INFO提示如果不需要反向映射推荐使用字符串枚举或常量枚举const enum编译后内联性能更好。3.1.7any类型any表示关闭该变量的类型检查可以赋任何值可以调用任何方法。仅在万不得已时使用。typescriptlet loose: any 42; loose now a string; // ✅ loose true; // ✅ loose.trim(); // ✅ 编译通过但若此时 loose 是数字运行时会崩溃⚠️ 使用any会让 TypeScript 的保护失效。一般只有以下情况才考虑迁移旧 JS 项目、与第三方非类型化库交互、或者你明确知道某个值类型复杂到无法描述但此时往往可以用unknown代替。3.1.8void类型void通常用于函数没有返回值。typescriptfunction logMessage(msg: string): void { console.log(msg); // 没有 return或者 return undefined; } let unusable: void undefined; // 声明 void 变量意义不大只能赋值 undefined 或 null如果 strictNullChecks 关闭3.1.9null和undefined在 TypeScript 中null和undefined既是值也是类型。typescriptlet n: null null; let u: undefined undefined; // 但在默认情况下strictNullChecks: falsenull 和 undefined 可以赋值给任何类型。 // 强烈建议开启 strictNullChecks这样只有明确标记了 | null 或 | undefined 的变量才能接收它们。3.1.10never类型never表示永远不会出现的值。它通常出现在两种场景函数抛出异常永不返回函数进入无限循环typescript// 抛出错误的函数返回类型是 never function throwError(message: string): never { throw new Error(message); } // 无限循环 function infiniteLoop(): never { while (true) { console.log(running); } }never还有一个重要用途穷尽性检查后面高级部分会详讲。3.1.11object类型object表示非原始类型即不是number、string、boolean、symbol、null、undefined的值。typescriptlet obj: object { name: Alice }; obj [1, 2, 3]; // ✅ 数组也是对象 obj () {}; // ✅ 函数也是对象 // obj 42; // ❌ 原始类型不行 // 更常见的做法是使用接口来描述对象的具体形状后面会讲3.2 类型推断TypeScript 会根据你的赋值自动推断类型你不需要为所有变量都加上注解。typescriptlet message Hello TS; // 推断为 string let count 10; // 推断为 number let flags [true, false]; // 推断为 boolean[] // 尝试重新赋值不兼容的类型会报错 // message 42; // ❌ 不能将 number 赋给 string // flags.push(string); // ❌ 不能将 string 赋给 boolean类型推断在大多数情况下已经足够但在变量声明时没有赋值或者需要明确的类型约束时才需要手动注解。3.3 联合类型联合类型用|表示表示一个值可以是几种类型之一。typescript// 一个变量可以是数字或字符串 let id: number | string; id 123; // ✅ id ABC456; // ✅ // id true; // ❌ // 函数参数使用联合类型 function printId(id: number | string): void { console.log(ID is: ${id}); } printId(101); printId(user-202);3.3.1 类型守卫收窄联合类型当你需要在函数内部区分联合类型的实际类型时可以使用typeof、instanceof或自定义类型守卫。typescriptfunction processValue(value: number | string): string { if (typeof value string) { // 在这个分支内TypeScript 知道 value 是 string return value.toUpperCase(); } else { // 这里 value 被收窄为 number return value.toFixed(2); } } console.log(processValue(hello)); // HELLO console.log(processValue(3.1415)); // 3.143.4unknown类型比any安全得多unknown可以理解为类型安全的any。它也可以接受任何值但在使用它之前你必须先进行类型检查。typescriptlet data: unknown fetchData(); // fetchData 返回 unknown // 下面的操作都不被允许因为 unknown 未经检查 // data.trim(); // ❌ 类型 unknown 上不存在属性 trim // data.toFixed(2); // ❌ // 必须先收窄类型 if (typeof data string) { data.trim(); // ✅ 此时 data 是 string } else if (typeof data number) { data.toFixed(2); // ✅ 此时 data 是 number }最佳实践对于来自外部 API 或用户输入的数据永远使用unknown而不是any。3.5 类型别名Type Aliases类型别名可以给一个类型起一个新名字方便复用。typescript// 联合类型别名 type ID number | string; type Status pending | success | error; // 函数类型别名 type Callback (result: string) void; // 使用别名 let userId: ID user123; let taskStatus: Status pending; let handler: Callback (res) console.log(res);类型别名非常灵活可以描述任何类型包括元组、联合、交叉等。3.6 可选属性和只读属性针对对象类型虽然对象类型的完整介绍在后面接口部分但这里先简单提一下基础用法typescript// 使用类型别名定义对象形状 type Person { name: string; age: number; email?: string; // 可选属性 readonly id: number; // 只读属性 }; const alice: Person { name: Alice, age: 25, id: 1001 // email 可以省略 }; // alice.id 1002; // ❌ 只读属性不能修改3.7 类型断言当你比 TypeScript 编译器更了解某个值的类型时可以使用类型断言告诉编译器“相信我它就是那个类型”。typescriptlet someValue: unknown this is a string; let strLength: number (someValue as string).length; // 尖括号语法在 .tsx 文件中不能用 let strLength2: number (stringsomeValue).length;注意类型断言不会改变运行时的值它只在编译时影响类型检查。如果你断言错误程序仍可能运行时崩溃。3.8 字面量类型TypeScript 支持将字符串、数字、布尔值本身作为类型。这常与联合类型配合使用实现类似枚举的效果。typescript// 字符串字面量类型 type Direction up | down | left | right; let move: Direction up; // ✅ // move north; // ❌ // 数字字面量类型 type Dice 1 | 2 | 3 | 4 | 5 | 6; let roll: Dice 4; // ✅ // roll 7; // ❌ // 布尔字面量类型 let isTrue: true true; // 只能赋值为 true几乎无用但理解概念3.9 类型系统小结现在你应该已经掌握了 TypeScript 的基础类型系统。下表总结了常用类型及其用途类型示例说明booleantrue/false布尔值number42, 3.14, 0xf00d所有数字stringhi, hi, \hi文本ArrayTnumber[]元素类型相同的列表[T, U][string, number]固定长度、固定顺序的元组enumenum Color { Red, Green }命名常量集合anyany任意值逃逸类型检查voidvoid无返回值nevernever永不存在的值unknownunknown任意值需要类型检查后使用|string | number联合类型typetype ID string | number类型别名掌握这些基础类型你已经能够为大多数 JavaScript 代码添加准确的类型注解了。下一步后续文章我们将深入函数、接口、类、泛型等内容。写在最后本文是 TypeScript 完整指南的上篇涵盖了为什么需要 TS、环境搭建以及最核心的基础类型系统。中篇我们将讨论函数类型、接口与类下篇则会深入泛型与高级类型。现在你可以动手在自己的项目里尝试用 TypeScript 编写几行代码感受一下类型检查带来的安全感。如果你坚持写完全文的所有示例相信你已经超越了 80% 的 TypeScript 初学者。 下篇预告函数类型、可选参数、重载接口与类高级类型交叉类型、索引类型等。敬请期待本回答由 AI 生成内容仅供参考请仔细甄别。