Skip to content

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 “这是一个对象”,但没有描述这个对象的结构。这为我们下一章学习 interfacetype 埋下了伏笔。

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。

    typescript
    let anything: any = 4;
    anything = 'a string'; // OK
    anything.someMethodThatDoesNotExist(); // OK (但在运行时会报错)

    何时使用? 在项目迁移初期,为了让老旧的、复杂的 JS 代码能快速跑起来,可能会临时使用 any。但我们的目标是最终消灭项目里所有的 any

  • unknown: any 的安全版本。和 any 一样,unknown 类型的变量可以接收任何值。但不同的是,在对它进行任何操作之前,你必须先进行类型收窄

    typescript
    let 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 vs unknownunknown 强迫你在使用前进行类型检查,这使得它比 any 安全得多。当你确实不知道一个值的类型时,请优先使用 unknown

  • void: 我们已经见过它了,代表函数没有返回值。

  • never: 一个比较高级的类型,它表示永远不会有返回值的函数类型。这通常发生在两种情况:

    1. 函数总是抛出异常。
    2. 函数内部是一个无限循环。
    typescript
    // 这个函数总是抛出错误,所以它永远不会正常返回
    function throwError(message: string): never {
      throw new Error(message);
    }
    
    // 这个函数永远不会结束
    function infiniteLoop(): never {
      while (true) {}
    }

总结与展望

恭喜你,你已经掌握了 TypeScript 的核心类型武器!我们学习了如何定义基础类型、数组,并利用联合类型处理多可能性。我们还领略了类型推断的智能,学会了如何为函数添加坚固的类型“护栏”,并认识了 anyunknown 等特殊工具。

现在,我们已经能为简单的值和函数逻辑提供类型保护了。但在真实世界里,数据往往是以复杂的对象形式存在的,比如一个包含用户名、ID、邮箱、地址等信息的用户对象。

如何优雅地定义这些复杂的数据结构呢?这正是我们下一篇文章要探讨的主题。在**《TypeScript 入门到实战(三):数据结构化 —— Interface 与 Type 的深度对决》**中,我们将学习 TypeScript 中最强大的两个工具:interfacetype

准备好为你的数据建模了吗?我们下一章见!