Typescript学习教程,从入门到精通,TypeScript 进阶语法知识点及案例代码详解(13)
TypeScript 进阶语法知识点及案例代码详解
本文将详细介绍 TypeScript 中关于类型兼容、类型操作、异步处理等核心概念,并结合具体案例代码进行深入解析。
1.1 接口兼容性(Interface Compatibility)
语法知识点:
- 结构化类型系统(Structural Typing):TypeScript 使用结构化类型系统,即只要两个类型的结构一致,它们就是兼容的,而不考虑它们的具体名称。
- 可选属性与额外属性:接口兼容性允许目标类型拥有源类型中不存在的属性,但源类型必须包含目标类型的所有必需属性。
案例代码:
// 定义两个接口
interface Person {name: string;age: number;
}interface Employee {name: string;age: number;department: string;
}// 函数接受 Person 类型参数
function greet(person: Person) {console.log(`Hello, ${person.name}`);
}// 创建一个 Employee 类型的对象
const employee: Employee = {name: "Alice",age: 30,department: "Engineering"
};// 将 Employee 类型的对象传递给接受 Person 类型参数的函数
greet(employee); // 合法,因为 Employee 包含 Person 的所有必需属性
解释:
Employee
接口包含Person
接口的所有属性,并且有额外的department
属性。- 由于 TypeScript 的结构化类型系统,
Employee
类型的对象可以赋值给Person
类型的参数,因为Employee
包含Person
的所有必需属性。
1.2 类兼容性(Class Compatibility)
语法知识点:
- 类的实例兼容性:类的实例兼容性主要基于其公共属性和方法,私有和受保护的成员不影响兼容性。
- 构造函数:类的构造函数不参与兼容性检查。
案例代码:
class Animal {public name: string;constructor(name: string) {this.name = name;}
}class Dog {public name: string;public breed: string;constructor(name: string, breed: string) {this.name = name;this.breed = breed;}
}let animal: Animal = new Dog("Buddy", "Golden Retriever"); // 合法,因为 Dog 包含 Animal 的所有公共属性
解释:
Dog
类包含Animal
类的所有公共属性 (name
),因此Dog
类型的实例可以赋值给Animal
类型的变量。- 额外的
breed
属性不影响兼容性。
1.3 函数兼容性(Function Compatibility)
语法知识点:
- 参数兼容性:目标函数的参数类型必须与源函数的参数类型兼容(目标函数可以有更多参数,但必须包含源函数的所有必需参数)。
- 返回类型兼容性:源函数的返回类型必须与目标函数的返回类型兼容。
案例代码:
// 定义两个函数类型
type GreetFunction = (name: string) => void;
type LogFunction = (message: string, level?: string) => void;// 实现函数
const greet: GreetFunction = (name) => {console.log(`Hello, ${name}`);
};const log: LogFunction = (message, level = "INFO") => {console.log(`${level}: ${message}`);
};// 函数赋值
let func1: GreetFunction = log; // 合法,因为 LogFunction 的参数兼容 GreetFunction
let func2: LogFunction = greet; // 不合法,因为 GreetFunction 的参数不包含 LogFunction 的第二个参数
解释:
func1
的赋值是合法的,因为LogFunction
的参数列表可以兼容GreetFunction
(即可以接受一个string
类型的参数)。func2
的赋值是不合法的,因为GreetFunction
不包含LogFunction
的第二个可选参数。
2. 类型操作(Type Operations)
2.1 联合类型(Union Types)
语法知识点:
- 联合类型:表示一个值可以是几种类型之一,使用
|
分隔。 - 类型保护:使用类型断言或类型守卫来缩小联合类型的范围。
案例代码:
// 定义联合类型
type StringOrNumber = string | number;// 函数接受联合类型参数
function printValue(value: StringOrNumber) {if (typeof value === "string") {console.log(`String: ${value}`);} else if (typeof value === "number") {console.log(`Number: ${value}`);} else {// 永远不会发生,因为已经覆盖了所有可能的类型const _exhaustiveCheck: never = value;throw new Error(`Unhandled type: ${typeof value}`);}
}printValue("Hello"); // 输出: String: Hello
printValue(42); // 输出: Number: 42
解释:
StringOrNumber
是一个联合类型,可以是string
或number
。- 在
printValue
函数中,使用typeof
操作符进行类型保护,以确定value
的具体类型。
2.2 交叉类型(Intersection Types)
语法知识点:
- 交叉类型:表示一个值同时具有几种类型的特性,使用
&
分隔。 - 组合类型:常用于组合多个对象的属性。
案例代码:
// 定义两个接口
interface Person {name: string;age: number;
}interface Employee {employeeId: number;department: string;
}// 定义交叉类型
type PersonEmployee = Person & Employee;// 创建对象
const personEmployee: PersonEmployee = {name: "Alice",age: 30,employeeId: 123,department: "Engineering"
};// 使用对象
console.log(personEmployee.name); // 输出: Alice
console.log(personEmployee.employeeId); // 输出: 123
解释:
PersonEmployee
是一个交叉类型,包含了Person
和Employee
接口的所有属性。personEmployee
对象必须同时满足Person
和Employee
的所有要求。
2.3 类型别名(Type Aliases)
语法知识点:
- 类型别名:为现有类型创建一个新名称,使用
type
关键字。 - 可重用性:提高代码的可读性和可维护性。
案例代码:
// 定义类型别名
type StringOrNumber = string | number;
type Coordinates = { x: number; y: number; };// 使用类型别名
function add(a: StringOrNumber, b: StringOrNumber): StringOrNumber {if (typeof a === "number" && typeof b === "number") {return a + b;}if (typeof a === "string" && typeof b === "string") {return a + b;}throw new Error("Invalid arguments");
}const position: Coordinates = { x: 10, y: 20 };
解释:
StringOrNumber
是string
和number
的联合类型。Coordinates
是包含x
和y
属性的对象类型。- 使用类型别名可以使代码更加简洁和易于理解。
2.4 类型推断(Type Inference)
语法知识点:
- 类型推断:TypeScript 自动推断变量的类型,无需显式声明。
- 最佳通用类型:推断出的类型是所有可能类型的最佳通用类型。
案例代码:
// 推断为 number 类型
let num = 42;// 推断为 string 类型
let str = "Hello";// 推断为 (a: number, b: number) => number 类型
let add = (a: number, b: number) => a + b;// 推断为 Array<string> 类型
let fruits = ["Apple", "Banana", "Cherry"];// 推断为 { name: string; age: number; } 类型
let person = {name: "Alice",age: 30
};
解释:
- TypeScript 根据初始化值的类型自动推断变量的类型。
- 在
add
函数中,返回类型也被推断为number
。
2.5 类型断言(Type Assertions)
语法知识点:
- 类型断言:告诉编译器某个值的具体类型,使用
as
关键字或尖括号语法。 - 谨慎使用:避免不安全地断言不正确的类型。
案例代码:
// 使用 as 关键字进行类型断言
let someValue: any = "this is a string";// 断言为 string 类型
let strLength: number = (someValue as string).length;// 使用尖括号语法进行类型断言
let strLength2: number = (<string>someValue).length;// 谨慎使用类型断言
function getLength(value: string | number): number {// 错误示例:断言为 string,但 value 也可能是 number// return (value as string).length;// 正确示例:使用类型保护if (typeof value === "string") {return value.length;}return 0;
}
解释:
someValue
被断言为string
类型,以便访问length
属性。- 在
getLength
函数中,不能直接断言value
为string
,因为它也可能是number
。应使用类型保护来安全地处理。
2.6 泛型(Generics)
语法知识点:
- 泛型:在定义函数、接口或类时,不预先指定具体的类型,而是在使用时指定类型。
- 类型参数:使用
<T>
来定义类型参数。
案例代码:
// 定义泛型函数
function identity<T>(arg: T): T {return arg;
}// 使用泛型函数
let num = identity<number>(42);
let str = identity<string>("Hello");// 类型推断也可以省略类型参数
let num2 = identity(42);
let str2 = identity("Hello");// 定义泛型接口
interface Box<T> {value: T;
}// 使用泛型接口
let boxNumber: Box<number> = { value: 100 };
let boxString: Box<string> = { value: "Box" };
解释:
identity
函数是一个泛型函数,可以接受任何类型的参数,并返回相同类型的值。- 使用泛型可以提高代码的复用性和灵活性。
Box
接口是一个泛型接口,可以容纳任何类型的值。
3. 错误处理(Error Handling)
3.1 传统回调函数实现异步处理(Traditional Callback Functions)
语法知识点:
- 回调函数:在异步操作完成后调用的函数。
- 错误优先回调:回调函数的第一个参数通常用于传递错误信息。
案例代码:
// 模拟异步操作
function fetchData(callback: (error: string | null, data: string | null) => void) {setTimeout(() => {const success = true; // 模拟成功或失败if (success) {callback(null, "Data fetched successfully");} else {callback("Error fetching data", null);}}, 1000);
}// 使用回调函数处理异步操作
fetchData((error, data) => {if (error) {console.error(`Error: ${error}`);} else {console.log(`Success: ${data}`);}
});
解释:
fetchData
函数模拟一个异步操作,使用回调函数来处理结果。- 回调函数的第一个参数用于传递错误信息,第二个参数用于传递成功的数据。
3.2 Promise 实现异步编程(Promise-Based Asynchronous Programming)
语法知识点:
- Promise:表示一个异步操作的最终完成或失败及其结果值。
- 链式调用:使用
.then()
和.catch()
进行链式调用。 - Promise 状态:pending、fulfilled、rejected。
案例代码:
// 模拟异步操作
function fetchData(): Promise<string> {return new Promise((resolve, reject) => {setTimeout(() => {const success = true; // 模拟成功或失败if (success) {resolve("Data fetched successfully");} else {reject("Error fetching data");}}, 1000);});
}// 使用 Promise 处理异步操作
fetchData().then((data) => {console.log(`Success: ${data}`);}).catch((error) => {console.error(`Error: ${error}`);});
解释:
fetchData
函数返回一个 Promise 对象,表示一个异步操作。- 使用
.then()
处理成功的结果,使用.catch()
处理错误。
3.3 async 和 await(Async and Await)
语法知识点:
- async 函数:声明一个异步函数,返回一个 Promise。
- await 表达式:等待一个 Promise 解决,并返回其结果。
- 错误处理:使用 try-catch 语句进行错误处理。
案例代码:
// 模拟异步操作
function fetchData(): Promise<string> {return new Promise((resolve, reject) => {setTimeout(() => {const success = true; // 模拟成功或失败if (success) {resolve("Data fetched successfully");} else {reject("Error fetching data");}}, 1000);});
}// 使用 async 和 await 处理异步操作
async function getData() {try {const data = await fetchData();console.log(`Success: ${data}`);} catch (error) {console.error(`Error: ${error}`);}
}getData();
解释:
getData
是一个 async 函数,使用await
等待fetchData
函数的 Promise 解决。- 使用 try-catch 语句来处理可能的错误。
4. 综合案例代码(Comprehensive Example)
以下是一个综合运用上述知识点的案例代码,展示了如何使用泛型、类型别名、错误处理和 async/await 来实现一个简单的数据获取函数。
// 定义类型别名
type FetchResult<T> = { data: T; error: string | null; };// 定义泛型函数
async function fetchData<T>(url: string): Promise<FetchResult<T>> {try {const response = await fetch(url);if (!response.ok) {return { data: null, error: `HTTP error! status: ${response.status}` };}const result: T = await response.json();return { data: result, error: null };} catch (error) {return { data: null, error: `Error: ${error}` };}
}// 使用 async 函数调用 fetchData
async function getUserData() {const url = "https://api.example.com/user/1";const result = await fetchData<{ id: number; name: string; age: number; }>(url);if (result.error) {console.error(result.error);} else {console.log(`User ID: ${result.data.id}, Name: ${result.data.name}, Age: ${result.data.age}`);}
}getUserData();
解释:
- 类型别名:
FetchResult<T>
是一个类型别名,表示一个包含data
和error
属性的对象,data
的类型由泛型参数T
决定。 - 泛型函数:
fetchData
是一个泛型函数,接受一个 URL 和一个类型参数T
,返回一个Promise<FetchResult<T>>
。 - 错误处理:在
fetchData
中,使用 try-catch 语句来捕获网络错误,并在返回结果中包含错误信息。 - async/await:
getUserData
是一个 async 函数,使用await
来调用fetchData
并处理结果。 - 调用示例:调用
getUserData
来获取用户数据,并打印出来。
总结
本文详细介绍了 TypeScript 中关于类型兼容、类型操作、错误处理和异步处理的核心概念,并通过具体的案例代码进行了深入解析。通过掌握这些高级特性,您可以编写出更加健壮、可维护和高效的 TypeScript 代码。