TypeScriptType:类型定义、声明与使用技巧
TypeScript 类型:定义、声明与使用技巧
TypeScript 的核心在于其强大的类型系统,它为 JavaScript 带来了静态类型检查的优势,提高了代码的可维护性、可读性和可靠性。理解 TypeScript 的类型定义、声明和使用技巧是掌握 TypeScript 的关键。本文将深入探讨这些方面,并通过示例帮助你更好地理解和应用 TypeScript 类型。
一、 类型定义:构建类型的基础
类型定义是 TypeScript 中描述数据结构和行为的方式。它告诉编译器变量、函数参数、函数返回值等应该是什么类型。TypeScript 提供了多种方式来定义类型:
- 基础类型 (Primitive Types):
TypeScript 内置了 JavaScript 的所有基础类型,并进行了一些扩展:
number
: 所有数字,包括整数和浮点数。string
: 文本字符串。boolean
: 布尔值,true
或false
。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
}
```
- 对象类型 (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 };
```
- 数组类型 (Array Types):
数组类型用于描述数组元素的类型。
```typescript
let numbers: number[] = [1, 2, 3];
let names: string[] = ["Alice", "Bob", "Charlie"];
// 使用泛型数组类型
let numbers2: Array
```
- 元组类型 (Tuple Types):
元组类型用于表示一个已知元素数量和类型的数组,每个元素的类型可以不同。
typescript
let coordinate: [number, number] = [10, 20];
let personData: [string, number, boolean] = ["Alice", 30, true];
- 枚举类型 (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,
};
```
- 字面量类型 (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;
```
-
泛型 (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 提供了多种方式进行类型声明:
- 类型注解 (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 };
```
- 类型推断 (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
}
```
类型推断使代码更简洁,同时仍然保持类型安全。但是,对于复杂的情况或者希望明确表达类型时,建议使用类型注解。
- 类型别名 (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";
```
- 接口 (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 代码:
-
尽可能使用类型注解: 虽然类型推断很方便,但在函数签名、复杂对象和公共 API 中明确指定类型可以提高代码的可读性和可维护性。
-
优先使用
interface
而不是type
来定义对象类型:interface
支持声明合并(declaration merging),这意味着你可以稍后向同一个接口添加更多属性。type
一旦定义就不能更改。 对于对象类型,interface
通常是更好的选择,除非你需要使用联合类型、交叉类型或其他高级类型特性。 -
利用类型别名简化复杂类型: 对于复杂的类型,例如联合类型、交叉类型或嵌套对象类型,使用类型别名可以提高代码的可读性。
-
使用泛型编写可重用代码: 泛型可以让你编写适用于多种类型的代码,而无需为每种类型编写单独的版本。
-
使用
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.
```
- 使用可选属性和可选参数: 使用
?
表示可选属性或可选参数,使类型定义更灵活。
```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!"
```
-
利用工具类型(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
中排除null
和undefined
。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,
};//其他工具类型用法类似,请参考官方文档
``` -
类型保护(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));
}
}
``` -
使用
as
进行类型断言 (Type Assertions):
类型断言允许你告诉编译器你比它更了解一个值的类型。类型断言有两种语法:
```typescript
// 尖括号语法
let someValue: any = "this is a string";
let strLength: number = (
// as 语法 (推荐)
let someValue2: any = "this is a string";
let strLength2: number = (someValue2 as string).length;
```
注意: 类型断言只是在编译时起作用,它不会进行运行时类型检查。如果你的断言是错误的,可能会导致运行时错误。因此,请谨慎使用类型断言,并确保你的断言是正确的。 优先使用类型保护,而不是类型断言。
四、总结
TypeScript 的类型系统是其核心特性,它为 JavaScript 代码带来了静态类型检查的优势,提高了代码的可维护性、可读性和可靠性。通过理解类型定义、声明和使用技巧,你可以充分利用 TypeScript 的强大功能,编写出更健壮、更易于维护的代码。 不断练习和探索 TypeScript 的类型系统,你会发现它是一个强大的工具,可以显著提高你的开发效率和代码质量。