TypeScript 教程 – wiki基地


拥抱更可靠的代码:一份全面的 TypeScript 入门与实践教程

作为一门构建于 JavaScript 之上的语言,TypeScript 在近年来受到了广泛关注和应用。它为 JavaScript 带来了静态类型检查、强大的工具支持以及更好的代码组织能力,极大地提升了大型应用开发的可维护性和可靠性。如果你是一名 JavaScript 开发者,想要提升自己的开发效率和代码质量,那么学习 TypeScript 绝对是一个明智的选择。

本文将带你从零开始,深入了解 TypeScript 的核心概念、基本语法以及如何在实际项目中应用它。无论你是刚接触 TypeScript 的新手,还是想系统性梳理知识点的开发者,这篇教程都将为你提供一份详尽的指南。

目录

  1. 为什么选择 TypeScript?

    • JavaScript 的“痛点”
    • TypeScript 如何解决问题
    • 主要优势:静态类型、强大的工具、可维护性
  2. 安装与设置

    • Node.js 与 npm/yarn/pnpm
    • 安装 TypeScript 编译器
    • 第一个 TypeScript 文件:hello.ts
    • 编译 TypeScript 代码
    • tsconfig.json 配置文件简介
  3. TypeScript 基础类型

    • 布尔值(boolean
    • 数字(number
    • 字符串(string
    • 数组(Array[]
    • 元组(Tuple
    • 枚举(enum
    • any 类型:慎用!
    • void 类型
    • nullundefined
    • never 类型
    • object 类型
  4. 变量与函数类型注解

    • 变量类型注解
    • 函数参数类型注解
    • 函数返回值类型注解
    • 可选参数与默认参数
    • 剩余参数
    • 函数重载
  5. 接口(Interfaces)

    • 什么是接口?
    • 定义对象形状
    • 可选属性与只读属性
    • 函数类型接口
    • 可索引签名
    • 扩展接口
    • 接口实现类
  6. 类(Classes)

    • ES6 Class 的基础上增强
    • 属性与方法
    • 构造函数
    • 继承
    • 修饰符(public, private, protected, readonly
    • 抽象类与抽象方法
  7. 类型别名(Type Aliases)

    • 使用 type 关键字
    • 与接口的区别和选择
  8. 联合类型(Union Types)与交叉类型(Intersection Types)

    • 联合类型(|):表示值可以是多种类型之一
    • 交叉类型(&):合并多种类型为一个新类型
  9. 字面量类型(Literal Types)

    • 将特定字符串、数字或布尔值作为类型
  10. 类型断言(Type Assertions)

    • 告诉编译器你比它更了解类型
    • 两种语法:as 关键字和 <> 语法
    • 使用场景与注意事项
  11. 泛型(Generics)

    • 为什么需要泛型?
    • 泛型函数
    • 泛型接口
    • 泛型类
    • 泛型约束
  12. 模块(Modules)

    • 使用 importexport
  13. 与 JavaScript 代码的协同

    • 声明文件(.d.ts
    • 使用 @types
  14. 深入 tsconfig.json

    • 文件结构与常用选项详解
    • strict 模式:为什么强烈推荐开启
    • target, module, outDir, rootDir
  15. TypeScript 在现代前端/后端框架中的应用(简述)

    • React, Vue, Angular
    • Node.js (Express, Koa, NestJS)
  16. 总结与下一步


1. 为什么选择 TypeScript?

JavaScript 是一门灵活、动态的语言,这使得它非常易于上手和快速开发原型。然而,当项目规模变大、团队成员增多时,JavaScript 的动态特性也带来了一些挑战:

  • 运行时错误: 很多类型错误或属性访问错误只有在代码运行时才会暴露,增加了调试成本。
  • 代码可读性与理解难度: 变量和函数没有明确的类型信息,需要通过阅读代码或文档来推断其预期的数据结构,增加了理解成本。
  • 重构困难: 修改代码时,很难确定一个改动会影响到哪些部分,容易引入新的 bug。
  • 工具支持受限: 编辑器和 IDE 难以提供精确的代码补全、导航和重构功能。

TypeScript(简称 TS)正是一种旨在解决这些问题的语言。它是 JavaScript 的一个超集,这意味着任何合法的 JavaScript 代码也是合法的 TypeScript 代码。TypeScript 在 JavaScript 的基础上添加了静态类型系统

静态类型意味着在代码编译阶段(而不是运行阶段)就可以检查类型错误。当你编写 TypeScript 代码时,TypeScript 编译器(tsc)会分析你的代码,并在发现类型不匹配或其他潜在问题时立即报错。

TypeScript 如何解决问题?

  • 提前发现错误: 大部分运行时错误在编译阶段就被捕获。
  • 提高代码可读性: 类型注解提供了代码的“契约”或“蓝图”,清晰地表明了变量、函数、对象等预期的结构。
  • 增强可维护性与重构安全性: 修改代码时,编译器会提示哪些地方因为类型不匹配而需要调整,大大降低了引入 bug 的风险。
  • 强大的工具支持: 基于类型信息,编辑器(如 VS Code, WebStorm)可以提供智能的代码补全、参数提示、错误检查、定义跳转、自动重构等功能,极大地提升开发效率。

主要优势总结:

  • 提高代码质量和可靠性: 静态类型检查是核心优势。
  • 提升开发效率: 得益于优秀的编辑器工具支持。
  • 改善团队协作: 类型信息作为一种契约,使得团队成员更容易理解和使用彼此的代码。
  • 拥抱最新 JavaScript 特性: TypeScript 支持最新的 ECMAScript 标准,并可以编译到旧版本的 JavaScript,解决兼容性问题。

2. 安装与设置

学习 TypeScript 的第一步是安装 TypeScript 编译器。

Node.js 与 npm/yarn/pnpm

TypeScript 编译器是一个 Node.js 包,因此你需要先安装 Node.js 环境。安装 Node.js 后,你将获得 npm(Node 包管理器)。或者你也可以选择使用 yarn 或 pnpm 作为包管理器。

安装 TypeScript 编译器

打开你的终端或命令行工具,运行以下命令全局安装 TypeScript:

bash
npm install -g typescript

或者使用 yarn:

bash
yarn global add typescript

安装完成后,你可以通过以下命令检查安装是否成功以及 TypeScript 的版本:

bash
tsc --version

第一个 TypeScript 文件:hello.ts

在你的代码编辑器中创建一个新文件,命名为 hello.ts。输入以下简单的 TypeScript 代码:

“`typescript
// hello.ts
function greet(person: string) {
return “Hello, ” + person;
}

let user = “TypeScript User”;
console.log(greet(user));

// 尝试传入错误类型的数据 (这将导致编译错误)
// let num = 123;
// console.log(greet(num)); // Argument of type ‘number’ is not assignable to parameter of type ‘string’.
“`

注意 greet 函数中的 : string,这就是一个类型注解,表明 person 参数期望一个字符串类型的值。

编译 TypeScript 代码

TypeScript 代码不能直接在浏览器或 Node.js 中运行(除非使用特定的运行时,如 ts-node)。它需要被编译成普通的 JavaScript 代码。使用 tsc 命令来编译你的 .ts 文件:

bash
tsc hello.ts

执行这个命令后,如果代码没有类型错误,编译器会在同一目录下生成一个同名的 JavaScript 文件:hello.js

javascript
// hello.js (由 tsc hello.ts 生成)
function greet(person) {
return "Hello, " + person;
}
var user = "TypeScript User";
console.log(greet(user));
// 尝试传入错误类型的数据 (这将导致编译错误)
// let num = 123;
// console.log(greet(num)); // Argument of type 'number' is not assignable to parameter of type 'string'.

可以看到,类型注解 : string 在编译后的 JavaScript 代码中被移除了,因为 JavaScript 本身不支持静态类型。

你现在可以使用 Node.js 运行生成的 hello.js 文件:

bash
node hello.js

输出应该是:Hello, TypeScript User

如果你取消注释 greet(num) 那一行,并再次运行 tsc hello.ts,编译器会在编译阶段报错,并告诉你 number 类型不能赋值给 string 类型,阻止你生成可能在运行时出错的 JavaScript 文件。

tsconfig.json 配置文件简介

对于大型项目,手动编译每个文件或使用复杂的命令行参数是不现实的。tsconfig.json 文件是 TypeScript 项目的配置文件,它告诉编译器如何编译项目中的 .ts 文件。

在项目根目录运行以下命令可以生成一个基本的 tsconfig.json 文件:

bash
tsc --init

这会生成一个包含许多选项的 tsconfig.json 文件,大部分初始状态是被注释掉的。你可以根据需要启用或修改这些选项。例如:

``json
// 部分 tsconfig.json 内容
{
"compilerOptions": {
/* Language and Environment Options */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"module": "commonjs", /* Specify what module code is generated. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target environment. */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the
checkJSoption to get errors from these files. */
// "checkJs": true, /* Enable error reporting in .js files. */
"strict": true, /* Enable all strict type-checking options. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables
allowSyntheticDefaultImports` for type compatibility. */

/* Emit */
// "outDir": "./dist",                    /* Specify an output folder for all emitted files. */
// "rootDir": "./src",                    /* Specify the root folder within your source files. */

/* Type Checking */
// "noImplicitAny": true,                 /* Enable error reporting for expressions and declarations with an implied `any` type. */
// "strictNullChecks": true,              /* When enabled, `null` and `undefined` are not in the domain of every type and need to be explicitly included. */
// ...更多选项

},
// “include”: [“src//“], / Specify an array of file patterns to include in the program. */
// “exclude”: [“node_modules”, “
/.spec.ts”] / Specify an array of file patterns to exclude from the program. */
}
“`

有了 tsconfig.json 文件后,只需在项目根目录运行 tsc 命令,编译器就会查找该文件并按照其中的配置来编译项目中的所有 TypeScript 文件。

3. TypeScript 基础类型

TypeScript 支持与 JavaScript 几乎相同的数据类型,并在此基础上添加了类型注解的能力。

  • 布尔值(boolean

    最基本的数据类型,只有 truefalse

    typescript
    let isDone: boolean = false;
    let isComplete = true; // 类型推断为 boolean

  • 数字(number

    所有数字都是浮点数,支持十进制、十六进制、二进制和八进制字面量。

    typescript
    let decimal: number = 6;
    let hex: number = 0xf00d;
    let binary: number = 0b1010;
    let octal: number = 0o744;
    let big: number = 100_000_000; // 数字分隔符

  • 字符串(string

    表示文本数据。可以使用单引号、双引号或模板字符串。

    typescript
    let color: string = "blue";
    let myName = 'Alice'; // 类型推断为 string
    let sentence: string = `Hello, my name is ${myName}.`; // 模板字符串

  • 数组(Array[]

    有两种方式表示数组:

    1. 元素类型后面接 []

      typescript
      let list: number[] = [1, 2, 3];

    2. 使用泛型 Array<元素类型>

      typescript
      let list2: Array<number> = [1, 2, 3];

    数组中的所有元素都必须是指定的类型。

  • 元组(Tuple

    元组类型允许你表示一个已知元素数量和类型的数组,各元素的类型不必相同。

    typescript
    // 定义一个元组,第一个元素是 string,第二个元素是 number
    let x: [string, number];
    // 初始化它
    x = ['hello', 10]; // OK
    // x = [10, 'hello']; // 错误:类型不匹配
    console.log(x[0].substring(1)); // OK,知道 x[0] 是 string
    // console.log(x[1].substring(1)); // 错误:知道 x[1] 是 number

    访问越界的元素会使用联合类型,通常是元组中所有类型的联合,这通常是不安全的,开启 strict 模式下会阻止这种行为。

  • 枚举(enum

    enum 类型是对 JavaScript 标准数据类型的一个补充。它允许你定义一组命名的常量。

    “`typescript
    enum Color {Red, Green, Blue}
    let c: Color = Color.Green; // 默认情况下,从 0 开始为元素编号

    console.log(c); // 输出 1

    // 你也可以手动指定成员的值
    enum Direction {Up = 1, Down, Left, Right}
    let d: Direction = Direction.Up; // d 的值是 1
    console.log(d); // 输出 1
    console.log(Direction.Down); // 输出 2

    // 或者全部手动指定
    enum FileAccess {
    None,
    Read = 1 << 1, // 位移运算符
    Write = 1 << 2,
    ReadWrite = Read | Write,
    // …
    }

    // 基于值获取成员名
    let colorName: string = Color[2];
    console.log(colorName); // 输出 “Blue”
    “`

    枚举在需要表示一组有限的、有意义的数值集合时非常有用。

  • any 类型:慎用!

    当你不知道变量会是什么类型,或者需要处理来自外部(如用户输入、第三方库)的动态内容时,可以使用 any 类型。any 类型可以绕过类型检查,允许你访问其任意属性或调用任意方法。

    “`typescript
    let notSure: any = 4;
    notSure = “maybe a string instead”;
    notSure = false; // boolean

    let list: any[] = [1, true, “free”];
    list[1] = 100;
    “`

    警告: 使用 any 会丢失 TypeScript 提供的类型检查优势。你应该尽量避免使用 any,除非你知道自己在做什么,或者在迁移旧的 JavaScript 项目时作为过渡。如果可能,尝试使用更具体的类型,如 unknown(更安全,后面会提到)或联合类型。

  • void 类型

    void 表示没有任何类型。通常用作函数没有返回值的类型。

    “`typescript
    function warnUser(): void {
    console.log(“This is a warning message”);
    }

    let unusable: void = undefined; // 在 strictNullChecks: false 的情况下,可以赋值 null 或 undefined
    // let unusable: void = null; // 在 strictNullChecks: false 的情况下
    // let unusable: void = 1; // 错误
    “`

  • nullundefined

    默认情况下,nullundefined 是所有类型的子类型。这意味着你可以将 nullundefined 赋值给 numberstring 等类型。

    “`typescript
    let u: undefined = undefined;
    let n: null = null;

    // let num: number = undefined; // 在 strictNullChecks: false 的情况下 OK
    // let str: string = null; // 在 strictNullChecks: false 的情况下 OK
    “`

    然而,当你在 tsconfig.json 中启用 strictNullChecks: true强烈推荐)时,nullundefined 只能赋值给它们自身的类型以及 voidany。这有助于防止常见的运行时错误(如访问 nullundefined 的属性)。

    “`typescript
    // 在 strictNullChecks: true 的情况下
    let num: number = 1;
    // num = undefined; // 错误
    // num = null; // 错误

    let str: string | null = “hello”; // 必须明确联合类型
    str = null; // OK
    “`

  • never 类型

    never 类型表示那些永不存在的值的类型。例如,总是抛出错误的函数表达式或箭头函数表达式的返回值类型,或者在类型守卫中被永不满足的类型。

    “`typescript
    // 返回 never 的函数必须包含无法到达的终点
    function error(message: string): never {
    throw new Error(message);
    }

    // 推断的返回值类型为 never
    function fail() {
    return error(“Something failed”);
    }

    // 返回 never 的函数示例:一个无限循环
    function infiniteLoop(): never {
    while (true) {
    }
    }
    “`

    never 是所有类型的子类型,但没有任何类型是 never 的子类型(除了 never 本身)。这使得 never 在处理穷尽检查时非常有用。

  • object 类型

    object 表示非原始类型,也就是除 number, string, boolean, symbol, null, undefined 之外的类型。

    “`typescript
    function create(o: object | null): void {
    // …
    }

    create({ prop: 0 }); // OK
    create(null); // OK
    // create(42); // 错误
    // create(“string”); // 错误
    // create(false); // 错误
    // create(undefined); // 错误 (除非 strictNullChecks: false)
    “`

4. 变量与函数类型注解

类型注解是 TypeScript 最显眼的功能之一。它使用 : type 的语法来指定变量、函数参数和函数返回值的类型。

  • 变量类型注解

    在变量声明后使用 : type

    typescript
    let age: number = 30;
    let name: string = "Bob";
    let isActive: boolean = true;
    let hobbies: string[] = ["reading", "gaming"];
    let person: { name: string, age: number } = { name: "Charlie", age: 25 };

    类型推断: 在很多情况下,TypeScript 可以根据变量的初始值自动推断出其类型,这时你就不需要显式地写出类型注解。这被称为类型推断。

    typescript
    let count = 10; // 推断为 number
    let greeting = "Hello"; // 推断为 string
    let isVisible = false; // 推断为 boolean

    尽管如此,对于函数的参数、返回值以及结构复杂的对象,显式地添加类型注解可以提高代码的可读性,并作为一种明确的文档。

  • 函数参数类型注解

    在函数参数名后使用 : type

    “`typescript
    function add(a: number, b: number): number {
    return a + b;
    }

    // add(5, “10”); // 错误:Argument of type ‘string’ is not assignable to parameter of type ‘number’.
    add(5, 10); // OK
    “`

  • 函数返回值类型注解

    在函数参数列表的圆括号后使用 : type

    “`typescript
    function greet(name: string): string {
    return “Hello, ” + name;
    }

    function logMessage(message: string): void { // 没有返回值,使用 void
    console.log(message);
    }

    // 函数表达式也可以
    const subtract = (a: number, b: number): number => a – b;
    “`

    TypeScript 也可以通过返回值推断出函数的返回类型,但显式声明返回值类型是一个很好的实践,尤其是在函数体比较复杂时。

  • 可选参数与默认参数

    在参数名后使用 ? 表示可选参数:

    “`typescript
    function buildName(firstName: string, lastName?: string): string {
    if (lastName) {
    return firstName + ” ” + lastName;
    } else {
    return firstName;
    }
    }

    console.log(buildName(“Bob”)); // OK
    console.log(buildName(“Bob”, “Adams”)); // OK
    // console.log(buildName(“Bob”, “Adams”, “Sr.”)); // 错误:参数过多
    “`

    为参数提供默认值时,该参数也会变成可选参数:

    “`typescript
    function buildNameWithDefault(firstName: string, lastName: string = “Smith”): string {
    return firstName + ” ” + lastName;
    }

    console.log(buildNameWithDefault(“Bob”)); // OK, lastName becomes “Smith”
    console.log(buildNameWithDefault(“Bob”, “Adams”)); // OK, lastName is “Adams”
    “`

  • 剩余参数(Rest Parameters)

    当不知道函数会有多少个参数时,可以使用剩余参数,它是一个数组:

    “`typescript
    function sum(…numbers: number[]): number {
    return numbers.reduce((total, num) => total + num, 0);
    }

    console.log(sum(1, 2, 3)); // 输出 6
    console.log(sum(1, 2, 3, 4, 5)); // 输出 15
    “`

  • 函数重载(Function Overloads)

    函数重载允许你为同一个函数提供多个函数类型定义,根据传入的参数数量或类型不同,执行不同的逻辑或具有不同的返回类型。

    你需要先定义一系列重载签名(只有参数和返回值类型,没有函数体),最后再定义一个实现签名(包含函数体,通常使用联合类型或 any 来兼容所有重载签名)。

    “`typescript
    // 重载签名
    function pickCard(x: number): number; // 输入数字,返回数字
    function pickCard(x: number[]): number; // 输入数字数组,返回数字
    function pickCard(x: string): string; // 输入字符串,返回字符串

    // 实现签名 (必须兼容所有重载签名)
    function pickCard(x: number | number[] | string): number | string {
    // 在这里实现函数逻辑
    if (typeof x === “number”) {
    return x * 2; // 假设返回一个数字
    } else if (Array.isArray(x)) {
    return x.length; // 假设返回数组长度
    } else if (typeof x === “string”) {
    return x.toUpperCase(); // 假设返回大写字符串
    }
    // 注意:实现签名本身不能被外部直接调用,类型检查只针对重载签名
    // 为了让编译器满意,可能需要处理所有分支或抛出错误
    throw new Error(“Invalid argument type”);
    }

    console.log(pickCard(10)); // 根据第一个签名,返回值推断为 number (实际输出 20)
    console.log(pickCard([1, 2, 3])); // 根据第二个签名,返回值推断为 number (实际输出 3)
    console.log(pickCard(“hello”)); // 根据第三个签名,返回值推断为 string (实际输出 “HELLO”)

    // pickCard(true); // 错误:没有匹配的重载签名
    “`
    函数重载主要用于提供更好的类型提示和检查,它并不会在运行时真正“重载”函数。

5. 接口(Interfaces)

接口是 TypeScript 中非常核心的概念,它用于定义对象的形状(Shape)。接口可以看作是一种契约,规定了实现该接口的对象必须包含哪些属性和方法及其类型。

  • 什么是接口?

    想象一下,你有一个函数需要一个配置对象作为参数,这个对象应该有哪些属性?每个属性的类型是什么?接口就是用来描述这个“应该有的形状”。

    “`typescript
    interface LabeledValue {
    label: string;
    }

    function printLabel(labeledObj: LabeledValue) {
    console.log(labeledObj.label);
    }

    let myObj = { size: 10, label: “Size 10 Object” };
    printLabel(myObj); // OK,myObj 的形状符合 LabeledValue 接口
    “`

    myObj 即使有 size 属性,只要它包含了 label 属性且类型为 string,就符合 LabeledValue 接口的要求。

  • 定义对象形状

    接口可以定义更复杂的对象形状,包括各种属性和方法。

    “`typescript
    interface Person {
    name: string;
    age: number;
    isStudent: boolean;
    address?: string; // 可选属性
    readonly id: number; // 只读属性
    greet(message: string): void; // 方法签名
    }

    let user: Person = {
    id: 123,
    name: “Alice”,
    age: 30,
    isStudent: false,
    greet: function(message: string) {
    console.log(${this.name} says: ${message});
    }
    };

    console.log(user.name);
    // user.id = 456; // 错误:id 是只读属性
    user.greet(“Hello!”);

    // 如果缺少必须属性会报错
    // let anotherUser: Person = { name: “Bob”, age: 25 }; // 错误:缺少 isStudent 和 greet 属性
    “`

  • 可选属性与只读属性

    • 可选属性 (?):表示该属性可有可无。
    • 只读属性 (readonly):表示属性在对象创建后就不能被修改。
  • 函数类型接口

    接口也可以用来描述函数的类型。

    “`typescript
    interface SearchFunc {
    (source: string, subString: string): boolean;
    }

    let mySearch: SearchFunc;
    mySearch = function(src: string, sub: string): boolean {
    let result = src.search(sub);
    return result > -1;
    };

    // mySearch = function(src: string, sub: number): boolean { … } // 错误:参数类型不匹配
    “`

  • 可索引签名

    描述那些可以通过索引来访问元素的对象,比如数组或字典(key-value pair)。

    “`typescript
    interface StringArray {
    [index: number]: string; // 数字索引,值是 string
    }

    let myArray: StringArray;
    myArray = [“Bob”, “Fred”];

    console.log(myArray[0]); // OK

    interface Dictionary {
    [key: string]: number; // 字符串索引,值是 number
    length: number; // 可以同时有其他属性,但其类型必须兼容索引签名的类型
    }

    let myDictionary: Dictionary = { “apple”: 1, “banana”: 2, length: 2 };
    // let badDictionary: Dictionary = { “apple”: 1, “banana”: “two” }; // 错误:值类型不匹配
    “`

    需要注意的是,如果同时使用数字索引和字符串索引,数字索引的返回值类型必须是字符串索引返回值类型的子类型。这是因为 JavaScript 会将数字索引转换为字符串(obj[1] 相当于 obj['1'])。

  • 扩展接口

    接口可以继承其他接口,创建更复杂的接口。

    “`typescript
    interface Shape {
    color: string;
    }

    interface Square extends Shape {
    sideLength: number;
    }

    let square: Square = {
    color: “blue”,
    sideLength: 10
    };

    // 接口可以继承多个接口
    interface PenStroke {
    penWidth: number;
    }

    interface SquareWithPen extends Square, PenStroke {
    // 同时拥有 color, sideLength, penWidth
    }

    let newSquare: SquareWithPen = {
    color: “red”,
    sideLength: 5,
    penWidth: 2
    };
    “`

  • 接口实现类

    类可以使用 implements 关键字来实现一个或多个接口,确保类符合接口定义的契约。

    “`typescript
    interface Clock {
    currentTime: Date;
    setTime(d: Date): void;
    }

    class DigitalClock implements Clock {
    currentTime: Date = new Date();
    setTime(d: Date) {
    this.currentTime = d;
    }
    constructor(h: number, m: number) {
    // … constructor logic
    }
    }

    // 类也可以实现多个接口
    interface Alarm {
    alarmTime: Date;
    }

    class CalendarAlarmClock implements Clock, Alarm {
    currentTime: Date = new Date();
    alarmTime: Date = new Date(); // 必须实现 Alarm 接口的属性

    setTime(d: Date) { // 必须实现 Clock 接口的方法
        this.currentTime = d;
    }
    
    constructor(date: Date) {
         this.currentTime = date;
         this.alarmTime = date; // 设置默认闹钟时间
    }
    // ... 其他属性和方法
    

    }
    “`

6. 类(Classes)

TypeScript 在 ECMAScript 2015(ES6)的 Class 语法基础上进行了增强,加入了强大的类型系统和访问修饰符。

  • 属性与方法

    “`typescript
    class Greeter {
    greeting: string; // 属性声明

    constructor(message: string) { // 构造函数
        this.greeting = message;
    }
    
    greet(): string { // 方法
        return "Hello, " + this.greeting;
    }
    

    }

    let greeter = new Greeter(“world”);
    console.log(greeter.greet()); // 输出 “Hello, world”
    “`

  • 继承

    使用 extends 关键字实现类的继承。派生类(子类)继承基类(父类)的属性和方法。在派生类的构造函数中,必须调用 super() 来调用基类的构造函数。

    ``typescript
    class Animal {
    name: string;
    constructor(theName: string) { this.name = theName; }
    move(distanceInMeters: number = 0) {
    console.log(
    ${this.name} moved ${distanceInMeters}m.`);
    }
    }

    class Snake extends Animal {
    constructor(name: string) { super(name); } // 调用父类构造函数
    move(distanceInMeters = 5) { // 重写 move 方法
    console.log(“Slithering…”);
    super.move(distanceInMeters); // 调用父类的 move 方法
    }
    }

    class Horse extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 45) { // 重写 move 方法
    console.log(“Galloping…”);
    super.move(distanceInMeters); // 调用父类的 move 方法
    }
    }

    let sam = new Snake(“Sammy the Python”);
    let tom: Animal = new Horse(“Tommy the Palomino”); // 可以将子类实例赋值给父类类型的变量

    sam.move();
    tom.move(34);
    “`

  • 修饰符(public, private, protected, readonly

    TypeScript 为类成员提供了访问修饰符,控制属性和方法的可访问性。

    • public (默认):在任何地方都可以访问。
    • private:只能在定义它的类内部访问。
    • protected:可以在定义它的类内部以及继承它的子类中访问。
    • readonly:属性只能在声明时或构造函数中被初始化,之后不能修改。

    “`typescript
    class Department {
    private employees: string[] = []; // private 只能在 Department 内部访问

    constructor(public name: string) { // public 作为构造函数参数,会自动创建并初始化同名 public 属性
    }
    
    addEmployee(employee: string) {
        this.employees.push(employee);
    }
    
    printEmployeeNames() {
        console.log(this.employees);
    }
    

    }

    let accounting = new Department(“Accounting”);
    accounting.addEmployee(“Bob”);
    accounting.addEmployee(“Anna”);
    accounting.printEmployeeNames(); // 输出 [“Bob”, “Anna”]

    // console.log(accounting.employees); // 错误:employees 是 private

    class ITDepartment extends Department {
    public admins: string[] = [];
    private lastReport: string; // private 属性

    constructor(name: string, admins: string[]) {
        super(name);
        this.admins = admins;
        this.lastReport = 'Initial Report'; // private 属性可以在构造函数中初始化
    }
    
    // protected 方法可以在子类中访问
    protected addEmployee(employee: string) {
         // ... 可以覆盖或增强父类的 protected 方法
         super.addEmployee(employee); // 调用父类的 protected 方法
    }
    
    // private 属性/方法不能在子类中直接访问
    // getReport() {
    //    console.log(this.lastReport); // 错误:lastReport 是 private
    // }
    

    }

    const it = new ITDepartment(“IT”, [“Max”]);
    // it.addEmployee(“Jane”); // 错误:addEmployee 在父类是 protected,子类继承过来也是 protected (如果子类没有覆盖) 或 public (如果子类覆盖并声明为 public)
    // 如果子类覆盖并声明为 public:
    class AnotherITDepartment extends Department {
    addEmployee(employee: string) { // 声明为 public
    super.addEmployee(employee);
    console.log(Added ${employee} to IT department.);
    }
    }
    const anotherIT = new AnotherITDepartment(“AnotherIT”);
    anotherIT.addEmployee(“Tom”); // OK
    “`

  • 抽象类与抽象方法

    抽象类不能被直接实例化,它主要用于定义派生类的基类。抽象方法只在抽象类中声明,不包含实现,必须在派生类中实现。

    “`typescript
    abstract class Shape {
    constructor(public color: string) { } // 属性可以在构造函数中声明并带有修饰符

    abstract getArea(): number; // 抽象方法,必须在子类中实现
    
    // 抽象类也可以有具体实现的方法
    toString(): string {
        return `Shape with color ${this.color}`;
    }
    

    }

    class Circle extends Shape {
    constructor(color: string, public radius: number) {
    super(color);
    }

    // 实现抽象方法 getArea
    getArea(): number {
        return Math.PI * this.radius * this.radius;
    }
    

    }

    class Rectangle extends Shape {
    constructor(color: string, public width: number, public height: number) {
    super(color);
    }

    // 实现抽象方法 getArea
    getArea(): number {
        return this.width * this.height;
    }
    

    }

    // const myShape = new Shape(“red”); // 错误:抽象类不能被实例化

    const myCircle = new Circle(“blue”, 5);
    console.log(myCircle.getArea());
    console.log(myCircle.toString());

    const myRectangle = new Rectangle(“green”, 10, 20);
    console.log(myRectangle.getArea());
    “`

7. 类型别名(Type Aliases)

type 关键字可以用来给任何类型创建一个新名字,也就是类型别名。类型别名不会创建一个新类型,它只是给现有类型提供了一个名字。

“`typescript
type Age = number;
type Name = string;
type IsStudent = boolean;

let myAge: Age = 30;
let myName: Name = “Alice”;
let studentStatus: IsStudent = true;

// 可以为联合类型创建别名
type StringOrNumber = string | number;
let value: StringOrNumber = “hello”;
value = 123; // OK

// 可以为对象形状创建别名 (与接口类似)
type Point = {
x: number;
y: number;
};

function printPoint(p: Point) {
console.log(x: ${p.x}, y: ${p.y});
}

printPoint({ x: 10, y: 20 });

// 可以为函数类型创建别名
type GreetFunc = (name: string) => string;

const greetingFn: GreetFunc = (personName) => Hello, ${personName}!;
console.log(greetingFn(“Bob”));
“`

类型别名 vs 接口

  • 接口(Interface) 主要用于定义对象的形状,类可以实现接口。
  • 类型别名(Type) 可以为任何类型创建别名,包括原始类型、联合类型、交叉类型、元组以及对象形状。

在定义对象形状时,接口通常是首选,因为它们可以被扩展 (extends) 和实现 (implements),这在面向对象和声明合并(Declaration Merging,多个同名接口会合并)方面有优势。而类型别名更适合用于组合其他类型(如联合类型、交叉类型)或为复杂的类型定义一个简短易懂的名字。

8. 联合类型(Union Types)与交叉类型(Intersection Types)

  • 联合类型(Union Types)

    使用 | 符号,表示一个值可以是多种类型之一。

    “`typescript
    function printId(id: number | string) {
    console.log(“Your ID is: ” + id);

    // 在使用联合类型的变量时,你需要进行类型缩小(Type Narrowing)
    // 比如使用 typeof 检查
    if (typeof id === "string") {
        console.log(id.toUpperCase()); // OK,现在编译器知道 id 是 string
    } else {
        console.log(id * 2); // OK,现在编译器知道 id 是 number
    }
    

    }

    printId(101);
    printId(“abc”);
    // printId(true); // 错误
    “`

    类型缩小是通过条件判断(如 typeof, instanceof, 属性存在检查等)来确定联合类型变量在某个代码块内的具体类型。

  • 交叉类型(Intersection Types)

    使用 & 符号,表示将多个类型合并为一个新类型。新类型拥有所有被合并类型的成员。

    “`typescript
    interface Left {
    left: number;
    }

    interface Right {
    right: number;
    }

    // Combined 类型同时拥有 left 和 right 属性
    type Combined = Left & Right;

    function useCombined(value: Combined) {
    console.log(Left: ${value.left}, Right: ${value.right});
    }

    useCombined({ left: 1, right: 2 }); // OK
    // useCombined({ left: 1 }); // 错误:缺少 right
    // useCombined({ right: 2 }); // 错误:缺少 left
    “`

    交叉类型常用于混合现有的类型,创建一个包含所有特性的新类型。

9. 字面量类型(Literal Types)

字面量类型允许你将一个特定的字符串、数字或布尔值作为类型。这通常与联合类型结合使用,来限制变量只能是几个预设的常量值之一。

“`typescript
let x: “hello” = “hello”;
// x = “world”; // 错误:Type ‘”world”‘ is not assignable to type ‘”hello”‘.

// 常用于函数参数,限制可能的输入值
function printText(s: string, alignment: “left” | “right” | “center”) {
// …
console.log(Text: ${s}, Alignment: ${alignment});
}

printText(“Hello”, “left”); // OK
// printText(“World”, “top”); // 错误:Type ‘”top”‘ is not assignable to type ‘”left” | “right” | “center”‘.

// 也可以用于数字字面量和布尔字面量
type DiceValue = 1 | 2 | 3 | 4 | 5 | 6;
let roll: DiceValue = 4;
// let invalidRoll: DiceValue = 7; // 错误

function getAnswer(answer: true): “yes” { // 参数只能是 true,返回值只能是 “yes”
return “yes”;
}

getAnswer(true); // OK
// getAnswer(false); // 错误
“`

字面量类型让你的代码表达力更强,并且可以在编译时捕获更多错误。

10. 类型断言(Type Assertions)

有时,你比 TypeScript 编译器更了解一个值的类型。类型断言就是用来告诉编译器“相信我,我知道这个值的类型是什么”。它不会在运行时产生任何影响,仅仅是给编译器提供一个提示。

类型断言有两种语法:

  1. 使用 as 关键字(JSX 中推荐使用这种方式,因为它避免了与 JSX 语法冲突)

    typescript
    let someValue: any = "this is a string";
    let strLength: number = (someValue as string).length;
    console.log(strLength); // 输出 16

  2. 使用尖括号 <> 语法(在 .ts 文件中也可以使用,但在 JSX 中与标签语法冲突)

    typescript
    let anotherValue: any = "this is also a string";
    let anotherLength: number = (<string>anotherValue).length;
    console.log(anotherLength); // 输出 16

    这两种语法是等价的。

使用场景:

  • 当你明确知道从 any 类型转换到具体类型时。
  • 当你从 DOM API 获取元素,并知道它是一个特定类型的元素时(例如,获取一个 canvas 元素并断言它是 HTMLCanvasElement)。
  • 在联合类型中,当你知道当前值是联合类型中的某个特定成员时。

    “`typescript
    interface Foo { foo: number }
    interface Bar { bar: string }

    type FooOrBar = Foo | Bar;

    function process(value: FooOrBar) {
    // console.log(value.foo); // 错误:FooOrBar 不一定有 foo 属性

    // 如果你知道 value 是 Foo
    console.log((value as Foo).foo); // OK (假设运行时 value 确实是 Foo)
    console.log((value as Bar).bar); // OK (假设运行时 value 确实是 Bar)
    

    }

    // 为了安全,通常结合类型守卫使用类型断言
    function processSafe(value: FooOrBar) {
    if ((value as Foo).foo !== undefined) { // 这是一个简单的属性检查类型守卫
    console.log((value as Foo).foo);
    } else {
    console.log((value as Bar).bar);
    }
    // 或者更好的类型守卫 function isFoo(v: any): v is Foo { return (v as Foo).foo !== undefined; }
    // if (isFoo(value)) { console.log(value.foo); } else { console.log(value.bar); }
    }
    “`

注意事项:

类型断言是强大的工具,但需要谨慎使用。它会绕过 TypeScript 的类型检查,如果你的断言是错误的(比如断言一个数字是字符串),那么运行时仍然会出错。类型断言只影响编译时,不影响运行时。

11. 泛型(Generics)

泛型是一种在定义函数、接口或类时不预先指定具体类型,而是在使用时再指定类型的机制。这使得代码可以适用于多种类型,同时仍然保持类型安全性。

  • 为什么需要泛型?

    考虑一个函数,它返回你传入的任何值(身份函数)。

    javascript
    function identity(arg) {
    return arg;
    }

    在 JavaScript 中,这很容易实现。但在 TypeScript 中,如果你不使用泛型,你可能会这样做:

    typescript
    function identityAny(arg: any): any {
    return arg;
    }
    let outputAny = identityAny("myString"); // outputAny 的类型是 any,丢失了信息
    console.log(outputAny.length); // OK,但运行时如果传入的是数字,这里就会报错

    使用 any 会丢失传入参数的类型信息。我们想要一个函数,它能接收任何类型,并且返回值的类型与输入参数的类型相同。这就是泛型的用武之地。

  • 泛型函数

    使用 <T>(或其他字母,通常用 T 表示 Type)作为类型变量,放在函数名后面。

    “`typescript
    function identity(arg: T): T {
    return arg;
    }

    // 方式一:编译器可以根据传入的值自动推断出 T 的类型
    let output1 = identity(“myString”); // T 被推断为 string
    console.log(output1.length); // OK

    let output2 = identity(123); // T 被推断为 number
    console.log(output2.toFixed(2)); // OK

    // 方式二:显式指定 T 的类型
    let output3 = identity(“another string”); // 显式指定 T 为 string
    let output4 = identity(456); // 显式指定 T 为 number

    // output3 = identity(123); // 错误:Type ‘number’ is not assignable to type ‘string’.
    “`

    <T> 中的 T 是一个占位符,代表一个类型,在使用函数时才确定具体类型。

  • 泛型接口

    接口也可以使用泛型。

    “`typescript
    interface GenericIdentityFn {
    (arg: T): T;
    }

    function identity(arg: T): T {
    return arg;
    }

    let myIdentity: GenericIdentityFn = identity; // 这里的 T 被指定为 number

    console.log(myIdentity(100)); // OK,输入和输出都是 number
    // console.log(myIdentity(“hello”)); // 错误:Argument of type ‘string’ is not assignable to parameter of type ‘number’.
    “`

    常见的内置泛型接口有 Array<T>Promise<T> 等。

    “`typescript
    let numList: Array = [1, 2, 3]; // T 是 number
    let strList: string[] = [“a”, “b”]; // 这是 Array 的简写形式

    async function fetchData(): Promise {
    // … fetch data
    return “data”;
    }

    let data: Promise = fetchData(); // Promise 解析后的值是 string
    “`

  • 泛型类

    类也可以使用泛型。

    “`typescript
    class GenericNumber {
    zeroValue: T;
    add: (x: T, y: T) => T;

    constructor(zero: T, addFn: (x: T, y: T) => T) {
         this.zeroValue = zero;
         this.add = addFn;
    }
    

    }

    let myGenericNumber = new GenericNumber(0, (x, y) => x + y);
    console.log(myGenericNumber.add(5, 10)); // OK, T 是 number

    let stringNumeric = new GenericNumber(“”, (x, y) => x + y);
    console.log(stringNumeric.add(“hello”, “world”)); // OK, T 是 string
    “`

    注意:类本身的静态成员不能使用类声明的泛型类型。

  • 泛型约束

    有时你希望泛型类型变量具有某个特定接口或类型,这时可以使用泛型约束。

    “`typescript
    interface Lengthwise {
    length: number;
    }

    // T 必须满足 Lengthwise 接口 (即必须有一个 number 类型的 length 属性)
    function loggingIdentity(arg: T): T {
    console.log(arg.length); // OK,现在知道 arg 有 length 属性
    return arg;
    }

    loggingIdentity({ length: 10, value: 3 }); // OK
    // loggingIdentity(3); // 错误:Argument of type ‘number’ is not assignable to parameter of type ‘Lengthwise’.
    // loggingIdentity(“hello”); // OK,字符串有 length 属性
    “`

12. 模块(Modules)

TypeScript 支持使用模块来组织代码。模块可以封装变量、函数、类、接口等,并将它们暴露给其他模块使用。TypeScript 遵从 ECMAScript 2015 的模块语法,使用 importexport 关键字。

  • 导出 (Export)

    在任何声明(变量、函数、类、类型别名、接口等)前加上 export 关键字。

    “`typescript
    // math.ts
    export const PI = 3.14159;

    export function add(x: number, y: number): number {
    return x + y;
    }

    export interface Serializable {
    serialize(): string;
    }

    export class MyClass implements Serializable {
    serialize() {
    return “serialized”;
    }
    }

    // 默认导出 (每个模块最多一个 default export)
    export default class Calculator {
    // …
    }
    “`

  • 导入 (Import)

    使用 import 关键字来导入其他模块导出的成员。

    “`typescript
    // main.ts
    import { PI, add, MyClass, Serializable } from “./math”; // 命名导入
    import Calculator from “./math”; // 默认导入
    // import * as math from “./math”; // 导入整个模块作为一个命名空间

    console.log(PI);
    console.log(add(2, 3));

    let myObj: Serializable = new MyClass();
    console.log(myObj.serialize());

    let calc = new Calculator();
    // …使用 calc
    “`

    在使用模块时,tsconfig.json 中的 module 选项非常重要,它决定了编译器生成哪种模块系统的代码(如 commonjs, esnext, amd 等)。现代前端开发通常使用 esnextes2020 等,由 Webpack、Rollup 或 Parcel 等打包工具进行处理。Node.js 环境则常用 commonjsesnext (配合 .mjs 文件或 "type": "module" 配置)。

13. 与 JavaScript 代码的协同

在实际项目中,你可能需要使用大量的 JavaScript 库(如 React, Lodash, jQuery)。TypeScript 如何知道这些库的类型信息呢?答案是声明文件(Declaration Files)

  • 声明文件(.d.ts

    声明文件以 .d.ts 为扩展名,它们只包含类型声明,没有具体的实现代码。这些文件描述了现有的 JavaScript 库或模块的类型信息,让 TypeScript 编译器能够理解它们。

    例如,一个简单的 jquery.d.ts 文件可能包含:

    “`typescript
    // 部分 jquery.d.ts
    interface JQuery extends Iterable {
    // … 许多方法和属性的签名
    html(htmlString: string): this;
    html(): string;
    css(propertyName: string | JQuery.PlainObject): string;
    css(propertyName: string, value: string | number): this;
    // …
    }

    // 定义全局变量 $ 和 jQuery 的类型
    declare const $: JQuery;
    declare const jQuery: JQuery;

    // 定义命名空间
    declare namespace JQuery {
    interface Event {
    // … 事件相关的类型
    }
    // … 更多定义
    }
    “`

    有了这样的声明文件,你在 TypeScript 代码中就可以像使用 TypeScript 库一样使用 jQuery,并获得完整的类型提示和检查。

    “`typescript
    // using-jquery.ts (需要安装 @types/jquery)
    // import $ from ‘jquery’; // 如果库是模块化的,这样导入

    $(‘#my-div’).html(‘Hello from TypeScript!’); // 类型检查通过
    // $(‘#my-div’).html(123); // 错误:Argument of type ‘number’ is not assignable to parameter of type ‘string’.
    “`

  • 使用 @types

    手动编写声明文件是非常繁琐的。幸运的是,社区维护了一个庞大的声明文件仓库:DefinitelyTyped。这个仓库中的声明文件通过 @types 组织在 npm 上。

    要为一个流行的 JavaScript 库安装其对应的类型声明文件,通常只需运行:

    bash
    npm install --save-dev @types/libraryname

    例如,为 jQuery 安装类型声明:

    bash
    npm install --save-dev @types/jquery

    为 React 安装类型声明:

    bash
    npm install --save-dev @types/react @types/react-dom

    安装后,TypeScript 编译器会自动在 node_modules/@types 目录中找到这些声明文件,从而为对应的 JavaScript 库提供类型信息。

14. 深入 tsconfig.json

tsconfig.json 文件是 TypeScript 项目的核心,它包含了编译器选项和项目文件配置。理解其中的关键选项对于有效地使用 TypeScript 至关重要。

生成 tsconfig.json 文件:tsc --init

“`json
// tsconfig.json (部分常用选项)
{
“compilerOptions”: {
/ 项目选项 /
“rootDir”: “./src”, // TypeScript 源代码的根目录
“outDir”: “./dist”, // 编译输出的 JavaScript 文件的目录
“declaration”: true, // 是否生成 .d.ts 声明文件 (对于库开发很重要)
“sourceMap”: true, // 是否生成 .map 文件 (用于调试)

/* 模块和目标 */
"target": "es2016", // 编译输出的 JavaScript 版本 (如 es5, es2015, esnext)
"module": "commonjs", // 编译输出的模块系统 (如 commonjs, esnext)
// "lib": [], // 包含哪些内置库的声明文件 (如 "es2015", "dom"),默认根据 target 推断
"esModuleInterop": true, // 简化 CommonJS 模块的导入 (allows `import * as foo from "foo"` and `import foo from "foo"` for CommonJS modules)
"allowSyntheticDefaultImports": true, // 允许从没有 default export 的模块进行 default import (结合 esModuleInterop 使用)

/* 严格类型检查选项 */
"strict": true, // **强烈推荐**:启用所有严格类型检查选项,包括以下选项
// "noImplicitAny": true, // 不允许隐式的 any 类型
// "strictNullChecks": true, // 启用严格的 null 和 undefined 检查
// "strictFunctionTypes": true, // 启用严格的函数类型检查
// "strictBindCallApply": true, // 启用严格的 bind, call, apply 方法检查
// "strictPropertyInitialization": true, // 检查类的非 undefined 属性是否在构造函数中初始化
// "noImplicitThis": true, // 检查 this 的隐式 any 类型
// "alwaysStrict": true, // 在编译输出的 JS 文件中启用 "use strict"

/* 其他类型检查选项 */
"noUnusedLocals": true, // 报告未使用的局部变量
"noUnusedParameters": true, // 报告未使用的函数参数
"noImplicitReturns": true, // 报告函数没有明确返回值的分支
"noFallthroughCasesInSwitch": true, // 检查 switch 语句中是否有未 break 的 case

/* JSX */
// "jsx": "react", // 支持 JSX 语法,并指定 JSX 编译方式

/* 路径映射 (Path Mapping) */
// "baseUrl": "./", // 用于解析非相对模块名的基目录
// "paths": {}, // 模块名到路径的映射,用于更方便的模块导入 (如 @/components 映射到 src/components)

},
“include”: [ // 哪些文件或目录需要编译
“src//*.ts”,
“src/
/*.tsx” // 如果使用 React
],
“exclude”: [ // 哪些文件或目录不需要编译 (优先级高于 include)
“node_modules”,
“dist”
]
}
“`

strict: true 选项:

这是最重要的一个选项。开启 strict: true 会同时启用许多严格的类型检查规则,如 noImplicitAny, strictNullChecks 等。虽然在迁移现有项目时可能需要逐步开启,但对于新项目,强烈建议一开始就启用 strict: true,这将为你提供最大的类型安全保障,减少潜在的运行时错误。

15. TypeScript 在现代前端/后端框架中的应用(简述)

TypeScript 已经成为现代 Web 开发的事实标准之一。

  • 前端框架:

    • Angular: Angular 完全使用 TypeScript 构建,并且官方推荐和鼓励使用 TypeScript 进行应用开发。
    • React: 通过安装 @types/react@types/react-dom,以及配置好 tsconfig.json (设置 "jsx": "react"),你就可以在 React 项目中使用 TypeScript 编写组件、Hooks 等,获得优秀的类型提示和错误检查。
    • Vue: Vue 3 核心和官方库大量使用了 TypeScript,对 TypeScript 的支持非常友好。你可以在 Vue CLI 或 Vite 创建项目时选择 TypeScript 模板。
  • 后端框架 (Node.js):

    • NestJS: 一个基于 Node.js 的渐进式框架,完全支持 TypeScript,提供了模块化、依赖注入等企业级特性。
    • Express/Koa: 通过安装 @types/express@types/koa,你可以在使用 Express 或 Koa 构建后端服务时享受到 TypeScript 的优势。可以使用 ts-node 直接运行 .ts 文件进行开发调试,最终编译成 JS 部署。

16. 总结与下一步

通过本文,我们了解了 TypeScript 的起源、核心优势,学习了基础类型、类型注解、接口、类、泛型等重要概念,并初步认识了 tsconfig.json 和如何在项目中集成 TypeScript。

TypeScript 为 JavaScript 世界带来了久违的类型安全和强大的工具支持,它能够帮助你构建更健壮、可读性更好、更易于维护的大型应用。虽然学习曲线相比纯 JavaScript 稍高,但投入的时间成本将在项目的长期开发和维护中获得丰厚的回报。

下一步:

  1. 实践: 创建一个小型项目,尝试应用本文学到的概念。从简单的功能开始,逐步增加复杂度。
  2. 阅读官方文档: TypeScript 官方文档(https://www.typescriptlang.org/docs/)是最好的参考资料,详细解释了所有功能。
  3. 了解更高级特性: TypeScript 还有很多高级特性,如类型守卫、类型推断的细节、条件类型、映射类型、装饰器等,可以在实践中逐步学习。
  4. 参与社区: 查看 GitHub 上的 TypeScript 项目,学习其他人的代码,或者参与到开源项目中。

TypeScript 是一门持续发展的语言,不断有新的特性加入。保持学习的热情,掌握 TypeScript,将极大地提升你的开发能力和职业竞争力。祝你在 TypeScript 的学习之旅中取得成功!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部