TypeScript 教程:从基础到进阶 – wiki基地


TypeScript 教程:从基础到进阶

TypeScript 是由 Microsoft 开发和维护的一种开源编程语言。它是 JavaScript 的一个超集,意味着任何有效的 JavaScript 代码都是有效的 TypeScript 代码。TypeScript 的主要目的是为 JavaScript 添加静态类型定义,从而在开发大型应用时提供更强的可维护性、可读性和更少的运行时错误。

I. 介绍与基础

1. 什么是 TypeScript?

  • 定义:TypeScript 是 JavaScript 的一个超集,它扩展了 JavaScript 的功能,最显著的特点是引入了静态类型。
  • 核心特性
    • 静态类型:允许你在开发阶段定义变量、函数参数和返回值的类型。
    • 更好的工具支持:得益于类型信息,IDE 可以提供更智能的代码补全、错误检查和重构功能。
    • 可伸缩性:类型系统使得大型项目的代码结构更清晰,更易于理解和维护。
  • 优势
    • 早期错误检测:在编译阶段而非运行时发现类型相关的错误。
    • 增强可读性:类型定义让代码意图更明确。
    • 提高开发效率:智能感知和自动补全减少了查找文档和手动纠错的时间。
    • 更好的协作:团队成员更容易理解和修改彼此的代码。
  • 与 JavaScript 的区别:JavaScript 是动态类型语言(运行时确定类型),而 TypeScript 是静态类型语言(编译时确定类型)。
  • TypeScript 编译器 (TSC):TypeScript 代码不能直接在浏览器或 Node.js 中运行。它需要通过 TypeScript 编译器(tsc)编译成纯 JavaScript 代码。

2. 设置开发环境

  1. 安装 Node.js 和 npm:TypeScript 编译和管理通常依赖于 Node.js 和其包管理器 npm(或 yarn)。访问 Node.js 官网 下载并安装。
  2. 安装 TypeScript
    bash
    npm install -g typescript # 全局安装
    # 或者在项目内安装
    npm install --save-dev typescript
  3. 初始化项目
    bash
    mkdir my-ts-project
    cd my-ts-project
    npm init -y
    npx tsc --init # 创建 tsconfig.json 文件
  4. tsconfig.json 配置:这是 TypeScript 项目的核心配置文件,用于指导编译器如何将 TypeScript 代码编译成 JavaScript。
    • "target": 指定编译后的 JavaScript 版本 (如 “es5”, “es2016”, “es2020”)。
    • "module": 指定模块系统 (如 “commonjs”, “esnext”)。
    • "outDir": 指定编译输出目录。
    • "strict": 启用所有严格类型检查选项 (强烈推荐)。
    • "rootDir": 指定 TypeScript 文件的根目录。
  5. 代码编辑器:推荐使用 VS Code,它对 TypeScript 有出色的内置支持,包括语法高亮、智能补全和错误提示。

3. 你的第一个 TypeScript 程序

  1. 创建 .ts 文件:在 my-ts-project 目录下创建一个 app.ts 文件。
    ``typescript
    // app.ts
    function greet(name: string) {
    return
    Hello, ${name}!`;
    }

    let user = “TypeScript”;
    console.log(greet(user));
    2. **编译和运行**:bash
    npx tsc app.ts # 编译 app.ts 生成 app.js
    node app.js # 运行编译后的 JavaScript 文件
    如果你配置了 `tsconfig.json` 并希望编译整个项目:bash
    npx tsc # 编译器会查找 tsconfig.json 并编译所有相关文件
    node dist/app.js # 假设 outDir 配置为 dist
    “`

II. TypeScript 基础

1. 基本类型

TypeScript 提供了以下内置类型:

  • string:表示文本数据。
    typescript
    let username: string = "Alice";
  • number:表示浮点数和整数。
    typescript
    let age: number = 30;
  • boolean:表示真/假值。
    typescript
    let isActive: boolean = true;
  • 类型推断:TypeScript 能够根据变量的初始值自动推断其类型。
    typescript
    let message = "Hello"; // message 会被推断为 string 类型
  • any:表示任意类型,可以接受任何值。应尽量避免使用 any,因为它会丧失 TypeScript 的类型检查优势。
    typescript
    let data: any = 10;
    data = "hello";
    data = true;
  • void:通常用于表示函数没有返回值。
    typescript
    function warnUser(): void {
    console.log("This is a warning.");
    }
  • undefinednull:它们各自是自己的类型,并且在 strictNullChecks 关闭时,可以赋值给其他类型。
    typescript
    let u: undefined = undefined;
    let n: null = null;
  • never:表示永远不会有值的类型,通常用于抛出异常或无限循环的函数返回值类型。
    typescript
    function error(message: string): never {
    throw new Error(message);
    }

2. 数组和元组

  • 数组:表示同类型元素的集合。
    typescript
    let numbers: number[] = [1, 2, 3];
    let names: Array<string> = ["Alice", "Bob"];
  • 元组 (Tuple):表示已知元素数量和类型的数组,各元素的类型不必相同。
    typescript
    let person: [string, number] = ["Alice", 30];
    // person = [30, "Alice"]; // Error: Type 'number' is not assignable to type 'string'

3. 函数

  • 函数类型注解:为函数参数和返回值添加类型。
    “`typescript
    function add(x: number, y: number): number {
    return x + y;
    }

    let sum = add(5, 3); // sum 的类型会被推断为 number
    * **可选参数**:使用 `?` 标记参数为可选。可选参数必须在必选参数之后。typescript
    function buildName(firstName: string, lastName?: string): string {
    if (lastName) {
    return firstName + ” ” + lastName;
    } else {
    return firstName;
    }
    }
    * **默认参数**:为参数提供默认值。typescript
    function sayHello(name: string = “World”): string {
    return Hello, ${name}!;
    }
    * **剩余参数**:使用 `...` 将多个参数收集到一个数组中。typescript
    function sumAll(firstNum: number, …restOfNumbers: number[]): number {
    return firstNum + restOfNumbers.reduce((acc, num) => acc + num, 0);
    }
    * **函数重载**:允许为同一个函数提供多个函数签名,以处理不同类型的输入。typescript
    function overloadExample(x: string): string;
    function overloadExample(x: number): number;
    function overloadExample(x: string | number): string | number {
    if (typeof x === ‘string’) {
    return x.toUpperCase();
    }
    return x * 2;
    }
    * **箭头函数**:与 ES6 相同,TypeScript 支持箭头函数,并且可以为它们添加类型。typescript
    let multiply = (a: number, b: number): number => a * b;
    “`

4. 对象

  • 对象类型定义:使用 {} 定义对象的结构和属性类型。
    typescript
    let user: { name: string; age: number } = {
    name: "Bob",
    age: 25
    };
  • 可选属性:使用 ? 标记对象的属性为可选。
    typescript
    let car: { brand: string; model: string; year?: number } = {
    brand: "Tesla",
    model: "Model 3"
    };

III. TypeScript 核心技能 (中级)

1. 接口 (Interfaces)

接口用于定义对象的结构、函数签名或类的契约。它们是 TypeScript 中最强大的类型检查机制之一。

“`typescript
// 定义一个接口
interface Person {
readonly id: number; // 只读属性
name: string;
age?: number; // 可选属性
greet(message: string): void; // 函数签名
}

// 实现接口的对象
let john: Person = {
id: 1,
name: “John Doe”,
greet(message: string) {
console.log(${this.name} says: ${message});
}
};

john.greet(“Hello!”);
// john.id = 2; // Error: Cannot assign to ‘id’ because it is a read-only property.

// 接口继承
interface Employee extends Person {
employeeId: string;
}

let jane: Employee = {
id: 2,
name: “Jane Smith”,
employeeId: “EMP001”,
greet(message: string) {
console.log(Employee ${this.name} says: ${message});
}
};
“`

2. 类型别名 (Type Aliases)

类型别名允许你为任何类型定义一个新名称,包括基本类型、联合类型、元组等。

“`typescript
type ID = string | number; // 为联合类型定义别名
type Point = { x: number; y: number }; // 为对象类型定义别名

let userId: ID = “abc-123”;
let userPoint: Point = { x: 10, y: 20 };

// 类型别名与接口的区别:
// 接口只能用于定义对象类型、函数类型。可以实现继承和合并。
// 类型别名可以用于定义任何类型,但不能被实现或合并。
“`

3. 联合类型 (Union) 和交叉类型 (Intersection)

  • 联合类型 (|):表示一个值可以是多种类型之一。
    typescript
    function printId(id: number | string) {
    console.log("Your ID is: " + id);
    }
    printId(101);
    printId("202");
  • 交叉类型 (&):将多个类型合并为一个类型,新类型将具有所有合并类型的特性。
    “`typescript
    interface Draggable {
    drag(): void;
    }

    interface Resizable {
    resize(): void;
    }

    type DraggableResizable = Draggable & Resizable;

    let uiElement: DraggableResizable = {
    drag() { console.log(“Dragging!”); },
    resize() { console.log(“Resizing!”); }
    };
    “`

4. 枚举 (Enums)

枚举允许你定义一组命名的常量。

“`typescript
// 数字枚举 (默认从 0 开始)
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
let go: Direction = Direction.Up;
console.log(go); // 0

// 自定义起始值
enum StatusCode {
NotFound = 404,
Success = 200,
Accepted = 202
}
console.log(StatusCode.NotFound); // 404

// 字符串枚举
enum LogLevel {
ERROR = “ERROR”,
WARN = “WARN”,
INFO = “INFO”,
DEBUG = “DEBUG”
}
console.log(LogLevel.ERROR); // “ERROR”

// 常量枚举 (编译后不会生成实际对象,直接替换为值)
const enum Color {
Red, Green, Blue
}
let c: Color = Color.Green;
“`

5. 类 (Classes)

TypeScript 对 ES6 的类进行了扩展,增加了类型注解、访问修饰符和接口实现等特性。

“`typescript
class Animal {
// 访问修饰符: public (默认), private, protected
private name: string; // 只能在类内部访问
protected species: string; // 只能在类内部和子类中访问

constructor(theName: string, theSpecies: string) {
    this.name = theName;
    this.species = theSpecies;
}

public move(distanceInMeters: number = 0) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
}

}

class Dog extends Animal {
constructor(name: string) {
super(name, “Canine”); // 调用父类构造函数
}

bark() {
    console.log('Woof! My species is ' + this.species); // 可以访问 protected 成员
}

}

let dog = new Dog(“Buddy”);
dog.bark();
dog.move(10);
// console.log(dog.name); // Error: Property ‘name’ is private.

// 实现接口
interface Greetable {
greeting: string;
greet(): void;
}

class Greeter implements Greetable {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
console.log(“Hello, ” + this.greeting);
}
}

// 抽象类:不能直接实例化,只能被继承
abstract class Department {
constructor(public name: string) {}
printName(): void {
console.log(“Department name: ” + this.name);
}
abstract printMeeting(): void; // 抽象方法必须在子类中实现
}

class AccountingDepartment extends Department {
constructor() {
super(“Accounting and Auditing”); // 构造函数中必须调用 super()
}
printMeeting(): void {
console.log(“The Accounting Department meets each Monday at 10am.”);
}
generateReports(): void {
console.log(“Generating accounting reports…”);
}
}
“`

6. 类型断言 (Type Assertions) 和类型守卫 (Type Guards)

  • 类型断言:告诉编译器你比它更了解某个值的类型。
    • “as” 语法 (推荐)
      typescript
      let someValue: any = "this is a string";
      let strLength: number = (someValue as string).length;
    • 尖括号语法 (不推荐与 JSX 混淆)
      typescript
      let someValue: any = "this is a string";
      let strLength: number = (<string>someValue).length;
  • 类型守卫:用于在运行时判断变量的类型,并在此特定作用域内确保其类型。
    • typeof 守卫
      typescript
      function padLeft(value: string, padding: string | number) {
      if (typeof padding === "number") {
      return Array(padding + 1).join(" ") + value;
      }
      if (typeof padding === "string") {
      return padding + value;
      }
      throw new Error(`Expected string or number, got '${padding}'.`);
      }
    • instanceof 守卫
      “`typescript
      class Fish { swim() {} }
      class Bird { fly() {} }

      function isFish(pet: Fish | Bird): pet is Fish {
      return (pet as Fish).swim !== undefined;
      }

      function getSmallPet(): Fish | Bird {
      return Math.random() > 0.5 ? new Fish() : new Bird();
      }

      let pet = getSmallPet();
      if (isFish(pet)) {
      pet.swim(); // pet 在此作用域内被推断为 Fish 类型
      } else {
      pet.fly(); // pet 在此作用域内被推断为 Bird 类型
      }
      ``
      * **用户自定义类型守卫**:通过返回
      parameterName is Type` 的函数来定义。

IV. 高级 TypeScript 概念

1. 泛型 (Generics)

泛型允许你编写可以适用于多种类型的组件,同时保持类型安全。它们在函数、接口和类中都很有用。

“`typescript
// 泛型函数
function identity(arg: T): T {
return arg;
}

let output1 = identity(“myString”); // 明确指定类型参数 T 为 string
let output2 = identity(123); // 类型推断,T 为 number

// 泛型接口
interface GenericIdentityFn {
(arg: T): T;
}
let myIdentity: GenericIdentityFn = identity;

// 泛型类
class GenericNumber {
zeroValue: T;
add: (x: T, y: T) => T;

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

}

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

// 泛型约束
interface Lengthwise {
length: number;
}

function loggingIdentity(arg: T): T {
console.log(arg.length); // 现在我们可以访问 .length 属性
return arg;
}

loggingIdentity(“hello”); // length: 5
loggingIdentity([1, 2, 3]); // length: 3
// loggingIdentity(3); // Error: Argument of type ‘number’ is not assignable to parameter of type ‘Lengthwise’.

// keyof 类型操作符
// keyof 操作符接受一个对象类型,生成其键的字符串或字符串字面量联合类型。
type PointKeys = keyof Point; // “x” | “y”
function getProperty(obj: T, key: K) {
return obj[key];
}
let p = { x: 10, y: 20 };
let xValue = getProperty(p, “x”); // xValue 的类型是 number
“`

2. 内置工具类型 (Utility Types)

TypeScript 提供了一系列内置工具类型,用于常见的类型转换操作。

  • Partial<T>: 将 T 的所有属性变为可选。
  • Required<T>: 将 T 的所有属性变为必选。
  • Readonly<T>: 将 T 的所有属性变为只读。
  • Pick<T, K>: 从 T 中选择 K 中列出的属性。
  • Omit<T, K>: 从 T 中删除 K 中列出的属性。
  • Exclude<T, U>: 从 T 中排除可分配给 U 的类型。
  • Extract<T, U>: 从 T 中提取可分配给 U 的类型。
  • NonNullable<T>: 从 T 中排除 nullundefined
  • Record<K, T>: 创建一个对象类型,其属性键为 K 类型,属性值为 T 类型。
  • ReturnType<T>: 获取函数类型 T 的返回值类型。
  • Parameters<T>: 获取函数类型 T 的参数类型(元组形式)。

“`typescript
interface Todo {
title: string;
description: string;
completed: boolean;
}

type PartialTodo = Partial;
// { title?: string; description?: string; completed?: boolean; }

type ReadonlyTodo = Readonly;
// { readonly title: string; readonly description: string; readonly completed: boolean; }

type TodoPreview = Pick;
// { title: string; completed: boolean; }

type TodoWithoutDescription = Omit;
// { title: string; completed: boolean; }

function createLogger(): (message: string) => void {
return (msg: string) => console.log(msg);
}
type LoggerFn = ReturnType; // (message: string) => void
“`

3. 装饰器 (Decorators)

装饰器是一种特殊类型的声明,它能够附加到类声明、方法、访问器、属性或参数上。装饰器使用 @expression 这种形式。

注意:装饰器目前仍是实验性特性,需要在 tsconfig.json 中启用 "experimentalDecorators": true"emitDecoratorMetadata": true

``typescript
// 方法装饰器
function LogMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(
Calling ${propertyKey} with arguments: ${JSON.stringify(args)});
const result = originalMethod.apply(this, args);
console.log(
${propertyKey} returned: ${JSON.stringify(result)}`);
return result;
};
return descriptor;
}

class Calculator {
@LogMethod
add(a: number, b: number): number {
return a + b;
}
}

const calc = new Calculator();
calc.add(2, 3);
// Output:
// Calling add with arguments: [2,3]
// add returned: 5
“`

4. 模块 (Modules) 和命名空间 (Namespaces)

  • 模块 (Modules):在现代 TypeScript/JavaScript 开发中,模块是组织代码的主要方式。每个文件都是一个模块,通过 importexport 关键字进行导入导出。
    • src/utils.ts
      typescript
      export function capitalize(str: string): string {
      return str.charAt(0).toUpperCase() + str.slice(1);
      }
      export const PI = 3.14159;
    • src/app.ts
      typescript
      import { capitalize, PI } from './utils';
      console.log(capitalize("hello")); // Hello
      console.log(PI); // 3.14159
  • 命名空间 (Namespaces):命名空间(前身为内部模块)是早期 TypeScript 中用于组织代码的方式,主要用于避免全局命名冲突。在现代应用中,通常推荐使用 ES 模块。命名空间主要用于大型全局库或旧代码库。

    “`typescript
    namespace MyValidation {
    export interface StringValidator {
    isAcceptable(s: string): boolean;
    }

    const lettersRegexp = /^[A-Za-z]+$/;
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }
    

    }

    let validator = new MyValidation.LettersOnlyValidator();
    console.log(validator.isAcceptable(“abc”)); // true
    “`

5. 高级类型操作

  • 条件类型 (Conditional Types):基于条件表达式的类型推断。
    “`typescript
    type TypeName =
    T extends string ? “string” :
    T extends number ? “number” :
    T extends boolean ? “boolean” :
    T extends undefined ? “undefined” :
    T extends Function ? “function” :
    “object”;

    type T1 = TypeName; // “string”
    type T2 = TypeName; // “object”

    // infer 关键字: 在条件类型中用于推断类型参数。
    type UnpackPromise = T extends Promise ? U : T;
    type A = UnpackPromise>; // string
    type B = UnpackPromise; // string[]
    * **映射类型 (Mapped Types)**:根据旧类型创建新类型,通常用于将旧类型的所有属性映射为新类型。typescript
    type Keys = “option1” | “option2”;
    type Flags = { [K in Keys]: boolean };
    // { option1: boolean; option2: boolean; }
    ``
    结合
    keyoftypeof` 可以创建非常灵活的类型。

6. 声明文件 (.d.ts)

当使用纯 JavaScript 编写的第三方库时,TypeScript 无法直接获取其类型信息。声明文件(.d.ts)提供了这些外部库的类型定义,让 TypeScript 编译器能够理解它们,从而在 TypeScript 项目中安全地使用这些库。

  • 环境声明:对于全局变量或没有模块导出的库。
    typescript
    // my-lib.d.ts
    declare var MY_GLOBAL_VAR: string;
    declare function myFunction(x: number): number;
  • 模块声明:为没有自带类型定义的 JavaScript 模块创建声明。
    typescript
    // types/some-module/index.d.ts
    declare module "some-module" {
    export function doSomething(arg: string): void;
    export class MyClass {
    constructor(name: string);
    getName(): string;
    }
    }
  • DefinitelyTyped:最大的高质量 TypeScript 声明文件仓库,通过 npm install @types/library-name 安装。

V. 实际应用与最佳实践

1. 与框架/库集成

TypeScript 与现代前端和后端框架配合得非常好:

  • React:通过 @types/react@types/react-dom,可以为组件的 props、state、hooks 和事件处理函数提供类型安全。
    typescript
    interface MyComponentProps {
    message: string;
    count: number;
    }
    const MyComponent: React.FC<MyComponentProps> = ({ message, count }) => {
    return <div>{message}: {count}</div>;
    };
  • Node.js/Express:通过 @types/node@types/express,为路由处理器、请求/响应对象等提供类型。
    typescript
    import express, { Request, Response } from 'express';
    const app = express();
    app.get('/', (req: Request, res: Response) => {
    res.send('Hello World!');
    });
  • Angular:Angular 是用 TypeScript 构建的,原生支持 TypeScript。
  • Vue.js:Vue 3 原生支持 TypeScript,提供 Composition API 更好地利用类型推断。

2. 项目结构和工具

  • 组织 TypeScript 项目:通常将 TypeScript 源文件放在 src/ 目录下,编译后的 JavaScript 和声明文件放在 dist/build/ 目录下。
  • Linting:使用 ESLint 结合 @typescript-eslint/parser@typescript-eslint/eslint-plugin 来执行代码风格和潜在错误的检查。
  • 构建工具
    • Webpack/Rollup/Vite.js:与 TypeScript 插件(如 ts-loaderrollup-plugin-typescript2)结合,将 TypeScript 文件打包成可在浏览器或 Node.js 中运行的 JavaScript。
    • tsc 命令行工具:对于简单的项目或库,直接使用 tsc 进行编译。
  • 测试 TypeScript 代码
    • Jest:结合 ts-jest 或直接使用 babel-jest,可以测试 TypeScript 代码。
    • Mocha/Chai:结合 ts-node 或预编译,也可以用于测试。

3. 最佳实践

  • 启用严格模式:在 tsconfig.json 中设置 "strict": true,这会启用所有严格的类型检查选项,有助于编写更健壮的代码。
  • 避免 any 类型:尽量减少 any 的使用,只在确实无法推断类型或需要与非 TypeScript 代码交互时使用。
  • 编写可维护和可伸缩的 TypeScript 代码
    • 清晰的接口和类型定义。
    • 利用泛型实现代码重用。
    • 合理组织模块和文件。
    • 为复杂的类型或函数添加 JSDoc 注释。
  • 将 JavaScript 项目迁移到 TypeScript
    1. 逐步引入:可以从更改文件扩展名 (.js.ts.jsx.tsx) 开始,TypeScript 会在非严格模式下推断类型。
    2. 添加 tsconfig.json
    3. 逐步添加类型注解,并启用更严格的类型检查选项。

4. 持续学习资源


希望这篇详细的 TypeScript 教程能帮助你从基础入门到掌握更高级的概念和实践!

滚动至顶部