Appearance
TypeScript 入门到实战(二):基础武器库 —— 掌握 TS 核心类型与函数
欢迎回来!在上一篇文章中,我们了解了 TypeScript 为何是 JavaScript 开发者的得力助手,并成功运行了第一个 TS 程序。我们知道了 Why,现在是时候深入学习 How 了。
如果说上一章是我们的“破冰之旅”,那么本章就是我们的“武器库扩充”。我们将一起锻造和掌握 TypeScript 中最核心、最常用的类型“兵器”,并学会如何用它们来武装我们的函数,让代码变得前所未有的坚固。
1. 基础类型 - 你最熟悉的伙伴
TypeScript 的基础类型与 JavaScript 的原始类型几乎一一对应,这让我们的学习曲线非常平滑。
typescript
let name: string = "Alice";
let age: number = 30;
let isStudent: boolean = false;
let u: undefined = undefined;
let n: null = null;
这些你都非常熟悉。但 TS 在此基础上提供了一些更精确的表达方式,尤其是对于数组和对象。
数组 (Array) 你想定义一个只包含数字的数组吗?在 JS 中这需要靠约定,但在 TS 中,这是一种规则。
有两种等效的写法:
typescript
// 写法一:类型 + 方括号 (推荐,更简洁)
let list: number[] = [1, 2, 3];
// 写法二:使用泛型
let list2: Array<number> = [1, 2, 3];
// 如果尝试添加其他类型的元素,会立刻得到错误提示!
// list.push('4'); // 错误: 类型“string”的参数不能赋给类型“number”的参数。
对象 (Object) 对于 object
类型,我们可以用它来表示一个非原始类型的值。
typescript
let person: object = { name: "Bob", age: 42 };
// person.name; // 错误: 类型“object”上不存在属性“name”。
你会发现,虽然 person
被正确地类型约束为一个对象,但你无法访问其内部的属性。这是因为 object
类型只告诉 TS “这是一个对象”,但没有描述这个对象的结构。这为我们下一章学习 interface
和 type
埋下了伏笔。
2. TS 的“读心术” - 类型推断 (Type Inference)
看到这里你可能会想:“难道我每声明一个变量都要写上类型吗?太繁琐了!”
别担心,TypeScript 远比你想象的要聪明。在很多情况下,它能根据上下文自动推断出变量的类型,这个过程就叫类型推断。
typescript
let message = "Hello, World!"; // TS 自动推断 message 的类型是 string
let count = 100; // TS 自动推断 count 的类型是 number
let isDone = true; // TS 自动推断 isDone 的类型是 boolean
// message = 200; // 错误: 不能将类型“number”分配给类型“string”。
最佳实践: 如果 TypeScript 能够自动推断出类型,就让它去做。我们通常只在函数参数、未初始化的变量或需要更明确表达意图的地方手动添加类型注解。
3. 当类型不唯一时 - 联合类型 (Union Types)
在 JavaScript 中,一个函数参数可能既能接收 string
类型,又能接收 number
类型,这很常见。在 TS 中,我们使用联合类型来优雅地处理这种情况。
联合类型使用 |
(管道符) 来分隔多个类型。
typescript
function printId(id: string | number) {
console.log(`Your ID is: ${id}`);
}
printId(101); // OK
printId("202-b"); // OK
// printId({ id: 3 }); // 错误: 类型“{ id: number; }”的参数不能赋给类型“string | number”的参数。
但这里有一个新问题:如果我想对 id
进行操作,比如转换成大写,该怎么办?
typescript
function printId(id: string | number) {
// console.log(id.toUpperCase());
// 错误: 类型“string | number”上不存在属性“toUpperCase”。
// 因为 number 类型没有 toUpperCase 方法
}
为了解决这个问题,TS 需要你先“收窄”类型范围,这个过程叫做类型收窄 (Type Narrowing)。简单来说,就是通过逻辑判断,让 TS 确定在某个代码块里,变量的具体类型是什么。
typescript
function printIdWithNarrowing(id: string | number) {
if (typeof id === "string") {
// 在这个 if 块里,TS 知道 id 100% 是 string 类型
console.log(id.toUpperCase());
} else {
// 在这个 else 块里,TS 知道 id 100% 是 number 类型
console.log(id.toFixed(2));
}
}
4. 为函数装上“护栏” - 函数类型化
函数是程序的核心,也是最容易出错的地方。TS 在函数上的应用,是它价值体现最明显的地方。
- 参数类型: 我们在上一章已经见识过了,它能确保我们传入正确的参数。
- 返回值类型: 我们可以(也应该)明确指定一个函数应该返回什么类型的值。
typescript
// (parameter: type): returnType
function add(x: number, y: number): number {
return x + y;
}
如果一个函数不返回任何值,我们使用 void
类型来表示。
typescript
function logMessage(message: string): void {
console.log(message);
// 这里不能有 return 语句返回值
}
可选参数 (?
) 和默认参数
与 JavaScript 类似,TS 也支持可选参数和默认参数。
typescript
// `lastName` 是可选的,可以不传
function buildName(firstName: string, lastName?: string): string {
if (lastName) {
return `${firstName} ${lastName}`;
} else {
return firstName;
}
}
// `greeting` 有默认值,不传时使用 'Hello'
// TS 会自动推断 greeting 是 string 类型
function greet(name: string, greeting = 'Hello'): string {
return `${greeting}, ${name}!`;
}
注意:可选参数必须在必选参数之后。
5. 特殊的“瑞士军刀” - any, unknown, void, never
除了基础类型,TS 还提供了一些特殊的工具类型,它们在特定场景下非常有用。
any
: 终极“后门”。当你把一个变量的类型设为any
,你其实是在告诉 TypeScript:“别管它了,我说了算!”。any
类型的变量可以被赋予任何类型的值,也可以调用任何方法,这等于放弃了类型检查,回到了纯粹的 JavaScript。typescriptlet anything: any = 4; anything = 'a string'; // OK anything.someMethodThatDoesNotExist(); // OK (但在运行时会报错)
何时使用? 在项目迁移初期,为了让老旧的、复杂的 JS 代码能快速跑起来,可能会临时使用
any
。但我们的目标是最终消灭项目里所有的any
。unknown
:any
的安全版本。和any
一样,unknown
类型的变量可以接收任何值。但不同的是,在对它进行任何操作之前,你必须先进行类型收窄。typescriptlet safer: unknown = 4; safer = 'a string'; // OK // let s: string = safer; // 错误: 不能将类型“unknown”分配给类型“string”。 if (typeof safer === 'string') { // 在这个块里,safer 被收窄为 string 类型 let s: string = safer; // OK console.log(s.toUpperCase()); // OK }
any
vsunknown
:unknown
强迫你在使用前进行类型检查,这使得它比any
安全得多。当你确实不知道一个值的类型时,请优先使用unknown
。void
: 我们已经见过它了,代表函数没有返回值。never
: 一个比较高级的类型,它表示永远不会有返回值的函数类型。这通常发生在两种情况:- 函数总是抛出异常。
- 函数内部是一个无限循环。
typescript// 这个函数总是抛出错误,所以它永远不会正常返回 function throwError(message: string): never { throw new Error(message); } // 这个函数永远不会结束 function infiniteLoop(): never { while (true) {} }
总结与展望
恭喜你,你已经掌握了 TypeScript 的核心类型武器!我们学习了如何定义基础类型、数组,并利用联合类型处理多可能性。我们还领略了类型推断的智能,学会了如何为函数添加坚固的类型“护栏”,并认识了 any
、unknown
等特殊工具。
现在,我们已经能为简单的值和函数逻辑提供类型保护了。但在真实世界里,数据往往是以复杂的对象形式存在的,比如一个包含用户名、ID、邮箱、地址等信息的用户对象。
如何优雅地定义这些复杂的数据结构呢?这正是我们下一篇文章要探讨的主题。在**《TypeScript 入门到实战(三):数据结构化 —— Interface 与 Type 的深度对决》**中,我们将学习 TypeScript 中最强大的两个工具:interface
和 type
。
准备好为你的数据建模了吗?我们下一章见!