为什么说"任何 JavaScript 开发者都应该学 TypeScript"?读完前三部分你就明白了。
很多初学者觉得 TypeScript 只是给 JS 加了个":string"的语法糖,但实际上它带来的收益远超你的想象。本文将带你从为什么需要 TS ,到环境搭建 ,再到基础类型系统 ,一步步建立起坚实的 TS 知识体系。本文不涉及任何前端框架(React/Vue 等),所有示例纯 TypeScript,可独立运行。
第一部分:为什么你需要 TypeScript?
1.1 一个"血泪史"案例引入
想象你在开发一个电商网站的促销模块,有一个函数用来计算订单总价:
javascript
// 这是纯 JavaScript
function calculateTotal(price, quantity, discount) {
return price * quantity - discount;
}
由于 JS 是动态类型,你可能会在运行时遇到各种诡异情况:
javascript
calculateTotal("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" → 180("20" 被隐式转换为数字)
这些错误只有在代码真正运行到那一步时才会暴露。如果测试覆盖不充分,它们就会悄悄溜到生产环境,变成用户的差评。
这就是 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 typescript
2.2 创建你的第一个 TypeScript 文件
新建一个 hello.ts:
typescript
// 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(推荐)
对于稍大一点的项目,你通常会需要一个配置文件来控制编译行为。
bash
tsc --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-node:
bash
npm install -D ts-node
npx ts-node hello.ts
它会直接执行 .ts 文件,适合快速验证代码。
第三部分:TypeScript 基础类型系统(全文重点)
TypeScript 继承了 JS 的所有数据类型,并额外增加了类型注解 和更多类型。下面逐一讲解。
3.1 八大基础类型
3.1.1 布尔类型 boolean
typescript
let isCompleted: boolean = false;
let isActive: boolean = true;
3.1.2 数字类型 number
包括整数、浮点数、十六进制、二进制、八进制等。
typescript
let decimal: number = 42;
let hex: number = 0xf00d; // 十六进制
let binary: number = 0b1010; // 二进制
let octal: number = 0o744; // 八进制
let float: number = 3.14159;
3.1.3 字符串类型 string
支持单引号、双引号和模板字符串。
typescript
let single: string = 'Hello';
let double: string = "World";
let template: string = `Hello ${single} ${double}!`;
3.1.4 数组类型 Array<T> 或 T[]
typescript
// 两种等价写法
let numbers1: number[] = [1, 2, 3, 4];
let numbers2: Array<number> = [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 开始):
typescript
enum 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"(反向映射)
字符串枚举(更易读):
typescript
enum LogLevel {
INFO = "INFO",
WARN = "WARN",
ERROR = "ERROR"
}
let level: LogLevel = LogLevel.INFO;
console.log(level); // "INFO"
提示:如果不需要反向映射,推荐使用字符串枚举或常量枚举(
const enum),编译后内联,性能更好。
3.1.7 any 类型
any 表示关闭该变量的类型检查 ,可以赋任何值,可以调用任何方法。仅在万不得已时使用。
typescript
let loose: any = 42;
loose = "now a string"; // ✅
loose = true; // ✅
loose.trim(); // ✅ 编译通过,但若此时 loose 是数字,运行时会崩溃
⚠️ 使用
any会让 TypeScript 的保护失效。一般只有以下情况才考虑:迁移旧 JS 项目、与第三方非类型化库交互、或者你明确知道某个值类型复杂到无法描述(但此时往往可以用unknown代替)。
3.1.8 void 类型
void 通常用于函数没有返回值。
typescript
function logMessage(msg: string): void {
console.log(msg);
// 没有 return,或者 return undefined;
}
let unusable: void = undefined; // 声明 void 变量意义不大,只能赋值 undefined 或 null(如果 strictNullChecks 关闭)
3.1.9 null 和 undefined
在 TypeScript 中,null 和 undefined 既是值,也是类型。
typescript
let n: null = null;
let u: undefined = undefined;
// 但在默认情况下(strictNullChecks: false),null 和 undefined 可以赋值给任何类型。
// 强烈建议开启 strictNullChecks,这样只有明确标记了 | null 或 | undefined 的变量才能接收它们。
3.1.10 never 类型
never 表示永远不会出现的值。它通常出现在两种场景:
-
函数抛出异常,永不返回
-
函数进入无限循环
typescript
// 抛出错误的函数,返回类型是 never
function throwError(message: string): never {
throw new Error(message);
}
// 无限循环
function infiniteLoop(): never {
while (true) {
console.log("running");
}
}
never 还有一个重要用途:穷尽性检查(后面高级部分会详讲)。
3.1.11 object 类型
object 表示非原始类型(即不是 number、string、boolean、symbol、null、undefined 的值)。
typescript
let obj: object = { name: "Alice" };
obj = [1, 2, 3]; // ✅ 数组也是对象
obj = () => {}; // ✅ 函数也是对象
// obj = 42; // ❌ 原始类型不行
// 更常见的做法是使用接口来描述对象的具体形状(后面会讲)
3.2 类型推断
TypeScript 会根据你的赋值自动推断类型,你不需要为所有变量都加上注解。
typescript
let 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 或自定义类型守卫。
typescript
function 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.14"
3.4 unknown 类型(比 any 安全得多)
unknown 可以理解为类型安全的 any。它也可以接受任何值,但在使用它之前,你必须先进行类型检查。
typescript
let 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 编译器更了解某个值的类型时,可以使用类型断言告诉编译器"相信我,它就是那个类型"。
typescript
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;
// 尖括号语法(在 .tsx 文件中不能用)
let strLength2: number = (<string>someValue).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 的基础类型系统。下表总结了常用类型及其用途:
| 类型 | 示例 | 说明 |
|---|---|---|
boolean |
true/false |
布尔值 |
number |
42, 3.14, 0xf00d |
所有数字 |
string |
"hi", 'hi', \hi`` |
文本 |
Array<T> |
number[] |
元素类型相同的列表 |
[T, U] |
[string, number] |
固定长度、固定顺序的元组 |
enum |
enum Color { Red, Green } |
命名常量集合 |
any |
any |
任意值(逃逸类型检查) |
void |
void |
无返回值 |
never |
never |
永不存在的值 |
unknown |
unknown |
任意值(需要类型检查后使用) |
| ` | ` | `string |
type |
`type ID = string | number` |
掌握这些基础类型,你已经能够为大多数 JavaScript 代码添加准确的类型注解了。下一步(后续文章)我们将深入函数、接口、类、泛型等内容。
写在最后
本文是 TypeScript 完整指南的上篇,涵盖了为什么需要 TS 、环境搭建 以及最核心的基础类型系统。中篇我们将讨论函数类型、接口与类,下篇则会深入泛型与高级类型。
现在,你可以动手在自己的项目里尝试用 TypeScript 编写几行代码,感受一下类型检查带来的安全感。如果你坚持写完全文的所有示例,相信你已经超越了 80% 的 TypeScript 初学者。
📌 下篇预告:函数类型、可选参数、重载;接口与类;高级类型(交叉类型、索引类型等)。敬请期待!
本回答由 AI 生成,内容仅供参考,请仔细甄别。