TypeScriptType:类型定义、声明与使用技巧

TypeScript 类型:定义、声明与使用技巧

TypeScript 的核心在于其强大的类型系统,它为 JavaScript 带来了静态类型检查的优势,提高了代码的可维护性、可读性和可靠性。理解 TypeScript 的类型定义、声明和使用技巧是掌握 TypeScript 的关键。本文将深入探讨这些方面,并通过示例帮助你更好地理解和应用 TypeScript 类型。

一、 类型定义:构建类型的基础

类型定义是 TypeScript 中描述数据结构和行为的方式。它告诉编译器变量、函数参数、函数返回值等应该是什么类型。TypeScript 提供了多种方式来定义类型:

  1. 基础类型 (Primitive Types):

TypeScript 内置了 JavaScript 的所有基础类型,并进行了一些扩展:

  • number: 所有数字,包括整数和浮点数。
  • string: 文本字符串。
  • boolean: 布尔值,truefalse
  • null: 表示一个空值或不存在的值。
  • undefined: 表示一个未初始化的值。
  • symbol: 表示唯一且不可变的值,通常用于对象属性的键。
  • bigint: 表示任意精度的整数 (ES2020 新增)。
  • void: 表示没有任何类型,通常用于函数没有返回值的情况。
  • never: 表示永不存在的值的类型,通常用于会抛出错误或永远不会返回的函数。
  • any: 表示任意类型,关闭类型检查。应尽量避免使用 any,除非你确实需要处理未知类型的数据。
  • unknown: 表示未知类型,比 any 更安全。 在对 unknown 类型的值执行任何操作之前,必须进行类型检查或类型断言。

```typescript
let age: number = 30;
let name: string = "Alice";
let isActive: boolean = true;
let data: null = null;
let missing: undefined = undefined;
let id: symbol = Symbol("id");
let bigNum: bigint = 100n;

function logMessage(message: string): void {
console.log(message);
}

function throwError(message: string): never {
throw new Error(message);
}

let anything: any = "可以是任何类型";
let unknownValue: unknown = 123;

// unknown 类型的操作需要类型检查
if (typeof unknownValue === 'number') {
let doubleValue = unknownValue * 2;
console.log(doubleValue); // 输出 246
}

```

  1. 对象类型 (Object Types):

对象类型用于描述对象的结构,包括属性的名称和类型。

```typescript
// 使用接口 (interface) 定义对象类型
interface Person {
firstName: string;
lastName: string;
age: number;
address?: string; // 可选属性
}

let person1: Person = {
firstName: "John",
lastName: "Doe",
age: 30,
};

let person2: Person = {
firstName: "Jane",
lastName: "Doe",
age: 25,
address: "123 Main St"
}

// 使用类型别名 (type alias) 定义对象类型
type Point = {
x: number;
y: number;
};

let point: Point = { x: 10, y: 20 };
```

  1. 数组类型 (Array Types):

数组类型用于描述数组元素的类型。

```typescript
let numbers: number[] = [1, 2, 3];
let names: string[] = ["Alice", "Bob", "Charlie"];

// 使用泛型数组类型
let numbers2: Array = [4, 5, 6];
```

  1. 元组类型 (Tuple Types):

元组类型用于表示一个已知元素数量和类型的数组,每个元素的类型可以不同。

typescript
let coordinate: [number, number] = [10, 20];
let personData: [string, number, boolean] = ["Alice", 30, true];

  1. 枚举类型 (Enum Types):

枚举类型用于定义一组命名的常量。

```typescript
enum Color {
Red, // 默认从 0 开始
Green,
Blue,
}

let myColor: Color = Color.Green;
console.log(myColor); // 输出 1

enum StatusCodes {
    OK = 200,
    NotFound = 404,
    InternalServerError = 500,
}
let responseStatus: StatusCodes = StatusCodes.OK;

```
6. 联合类型 (Union Types):

联合类型表示一个值可以是多种类型之一。

```typescript
let id: number | string;
id = 123; // OK
id = "abc"; // OK

function printId(id: number | string) {
    if (typeof id === "string") {
        console.log(id.toUpperCase());
    } else {
        console.log(id);
    }
}

```
7. 交叉类型 (Intersection Types):

交叉类型将多个类型合并为一个类型。
```typescript
 interface A {
    propA: string;
}

interface B {
    propB: number;
}

type Combined = A & B;

let obj: Combined = {
    propA: "hello",
    propB: 123,
};
```
  1. 字面量类型 (Literal Types):

字面量类型允许你指定一个变量必须是某个特定的字符串、数字或布尔值。

```typescript
let direction: "north" | "south" | "east" | "west";
direction = "north"; // OK
// direction = "up"; // Error: Type '"up"' is not assignable to type '"north" | "south" | "east" | "west"'.

let diceRoll: 1 | 2 | 3 | 4 | 5 | 6;

type SuccessResponse = {
success: true;
data: any;
};

type ErrorResponse = {
    success: false;
    error: string;
};

type ApiResponse = SuccessResponse | ErrorResponse;

```

  1. 泛型 (Generics):
    泛型允许你编写可重用的组件,这些组件可以支持多种类型,而不仅仅是单一类型。 泛型使用类型参数(通常用 <T> 表示,但也可以使用其他字母)来表示类型。

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

    let myString = identity("hello"); // myString 的类型是 string
    let myNumber = identity(123); // myNumber 的类型是 number
    let myBoolean = identity(true); // 类型推断,myBoolean 的类型是 boolean

    //泛型接口
    interface KeyValuePair {
    key: K;
    value: V;
    }
    let pair1: KeyValuePair = { key: 1, value: "one" };
    let pair2: KeyValuePair = { key: "isActive", value: true };

    //泛型类
    class DataHolder {
    data: T;

    constructor(data: T) {
        this.data = data;
    }
    
    getData(): T {
        return this.data;
    }
    

    }

    let stringHolder = new DataHolder("Hello");
    let numberHolder = new DataHolder(123);

    ```

二、 类型声明:为代码添加类型信息

类型声明是告诉 TypeScript 变量、函数、类等的类型信息的过程。TypeScript 提供了多种方式进行类型声明:

  1. 类型注解 (Type Annotations):

类型注解是最直接的类型声明方式,使用冒号 : 在变量、函数参数、函数返回值等后面指定类型。

```typescript
let count: number = 10;

function greet(name: string): string {
return "Hello, " + name;
}

let coordinates: { x: number; y: number } = { x: 10, y: 20 };
```

  1. 类型推断 (Type Inference):

TypeScript 可以根据上下文自动推断出变量或表达式的类型,无需显式指定类型注解。

```typescript
let message = "Hello, TypeScript!"; // TypeScript 推断 message 的类型为 string
let sum = 10 + 20; // TypeScript 推断 sum 的类型为 number

function add(a: number, b: number) {
return a + b; // TypeScript 推断返回类型为 number
}
```

类型推断使代码更简洁,同时仍然保持类型安全。但是,对于复杂的情况或者希望明确表达类型时,建议使用类型注解。

  1. 类型别名 (Type Aliases):

类型别名使用 type 关键字为现有类型创建一个新的名称。这可以提高代码的可读性和可维护性。

```typescript
type Point = {
x: number;
y: number;
};

type StringOrNumber = string | number;

let origin: Point = { x: 0, y: 0 };
let value: StringOrNumber = "hello";
```

  1. 接口 (Interfaces):

接口使用 interface 关键字定义对象的结构。 接口可以被类实现(implements),也可以用于类型注解。

```typescript
interface Animal {
name: string;
makeSound(): void;
}

class Dog implements Animal {
    name: string;

    constructor(name: string) {
        this.name = name;
    }

    makeSound(): void {
        console.log("Woof!");
    }
}

let myDog: Animal = new Dog("Buddy");
myDog.makeSound(); // 输出 "Woof!"

```
5. 声明文件 (Declaration Files):

声明文件(`.d.ts` 文件)用于为已有的 JavaScript 代码提供类型信息,以便 TypeScript 能够理解和检查这些代码。  这对于使用第三方库或模块非常重要。

```typescript
// my-library.d.ts
declare module "my-library" {
    export function greet(name: string): string;
    export const version: string;
}
```
许多流行的 JavaScript 库都有对应的社区维护的声明文件,可以通过 `@types` 包安装,例如:
```bash
npm install --save-dev @types/react
npm install --save-dev @types/node
```

三、 类型使用技巧:提升代码质量

掌握以下类型使用技巧可以帮助你编写更健壮、更易于维护的 TypeScript 代码:

  1. 尽可能使用类型注解: 虽然类型推断很方便,但在函数签名、复杂对象和公共 API 中明确指定类型可以提高代码的可读性和可维护性。

  2. 优先使用 interface 而不是 type 来定义对象类型: interface 支持声明合并(declaration merging),这意味着你可以稍后向同一个接口添加更多属性。type 一旦定义就不能更改。 对于对象类型,interface 通常是更好的选择,除非你需要使用联合类型、交叉类型或其他高级类型特性。

  3. 利用类型别名简化复杂类型: 对于复杂的类型,例如联合类型、交叉类型或嵌套对象类型,使用类型别名可以提高代码的可读性。

  4. 使用泛型编写可重用代码: 泛型可以让你编写适用于多种类型的代码,而无需为每种类型编写单独的版本。

  5. 使用 readonly 修饰符: 对于不应该被修改的属性,使用 readonly 修饰符可以防止意外的修改。

```typescript
interface Config {
readonly apiKey: string;
readonly baseUrl: string;
}

const config: Config = {
apiKey: "YOUR_API_KEY",
baseUrl: "https://api.example.com",
};

// config.apiKey = "NEW_API_KEY"; // Error: Cannot assign to 'apiKey' because it is a read-only property.
```

  1. 使用可选属性和可选参数: 使用 ? 表示可选属性或可选参数,使类型定义更灵活。

```typescript
interface User {
id: number;
name: string;
email?: string; // 可选属性
}

function greet(name: string, greeting?: string) {  //可选参数
    if (greeting) {
        console.log(`${greeting}, ${name}!`);
    } else {
        console.log(`Hello, ${name}!`);
    }
}

greet("Alice");           // 输出 "Hello, Alice!"
greet("Bob", "Good morning"); // 输出 "Good morning, Bob!"

```

  1. 利用工具类型(Utility Types): TypeScript 提供了一组内置的工具类型,可以帮助你进行类型转换和操作。

    • Partial<T>: 将类型 T 的所有属性变为可选。
    • Required<T>: 将类型 T 的所有属性变为必选。
    • Readonly<T>: 将类型 T 的所有属性变为只读。
    • Pick<T, K>: 从类型 T 中选取指定的属性 K
    • Omit<T, K>: 从类型 T 中排除指定的属性 K
    • Record<K, T>: 创建一个键为 K 类型,值为 T 类型的对象类型。
    • Exclude<T, U>: 从类型 T 中排除可以赋值给类型 U 的类型。
    • Extract<T, U>: 从类型 T 中提取可以赋值给类型 U 的类型。
    • NonNullable<T>: 从类型 T 中排除 nullundefined
    • Parameters<T>: 获取函数类型 T 的参数类型,返回一个元组类型。
    • ReturnType<T>: 获取函数类型 T 的返回值类型。

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

    // 使用 Partial 创建一个所有属性都可选的新类型
    type PartialTodo = Partial;
    // 等价于:
    // type PartialTodo = {
    // title?: string;
    // description?: string;
    // completed?: boolean;
    // }

    let partialTodo: PartialTodo = { title: "Learn TypeScript" }; // OK

    // 使用 Pick 从 Todo 中选取 title 和 completed 属性
    type TodoPreview = Pick;
    // type TodoPreview = {
    // title: string;
    // completed: boolean;
    // }

    // 使用 Record 创建一个键为 string 类型,值为 number 类型的对象
    let scores: Record = {
    Alice: 90,
    Bob: 85,
    };

    //其他工具类型用法类似,请参考官方文档
    ```

  2. 类型保护(Type Guards):
    类型保护是一种缩小类型范围的技术,通常用于联合类型。 常见的类型保护方法有:

    • typeof: 用于检查基本类型。
    • instanceof: 用于检查对象的实例类型。
    • 用户定义的类型保护函数: 通过返回一个类型谓词 arg is Type 来实现。

    ```typescript
    function isString(value: any): value is string { //类型谓词
    return typeof value === "string";
    }

    function processValue(value: string | number) {
    if (isString(value)) {
    // 在这个代码块中,TypeScript 知道 value 的类型是 string
    console.log(value.toUpperCase());
    } else {
    // 在这个代码块中,TypeScript 知道 value 的类型是 number
    console.log(value.toFixed(2));
    }
    }
    ```

  3. 使用 as 进行类型断言 (Type Assertions):

类型断言允许你告诉编译器你比它更了解一个值的类型。类型断言有两种语法:

```typescript
// 尖括号语法
let someValue: any = "this is a string";
let strLength: number = (someValue).length;

// as 语法 (推荐)
let someValue2: any = "this is a string";
let strLength2: number = (someValue2 as string).length;
```
注意: 类型断言只是在编译时起作用,它不会进行运行时类型检查。如果你的断言是错误的,可能会导致运行时错误。因此,请谨慎使用类型断言,并确保你的断言是正确的。 优先使用类型保护,而不是类型断言。

四、总结

TypeScript 的类型系统是其核心特性,它为 JavaScript 代码带来了静态类型检查的优势,提高了代码的可维护性、可读性和可靠性。通过理解类型定义、声明和使用技巧,你可以充分利用 TypeScript 的强大功能,编写出更健壮、更易于维护的代码。 不断练习和探索 TypeScript 的类型系统,你会发现它是一个强大的工具,可以显著提高你的开发效率和代码质量。

THE END