跟着坚果派社区从零开始学鸿蒙——ArkTS泛型全解析:让代码更加灵活强大
🚀 ArkTS泛型全解析:让代码更加灵活强大 🚀
💫 干货分享! 泛型是现代编程语言的核心特性,掌握它能让你的代码更加灵活、可复用。今天就带大家深入了解ArkTS中的泛型类型和函数,解锁代码新境界~
📌 泛型的基本概念:一次编写,多种类型
泛型允许我们编写可以适用于多种类型的代码,而不仅限于单一类型。它就像是代码的"模板",可以根据需要填入不同的类型。
🌟 泛型的核心优势:
- 代码复用性更高
- 类型安全性得到保障
- 减少重复代码
- 提高代码可维护性
📌 泛型类和接口:灵活的类型定义
🔸 泛型类
泛型类通过在类名后添加类型参数来定义:
class CustomStack<Element> {
private elements: Element[] = [];
public push(e: Element): void {
this.elements.push(e);
}
public pop(): Element | undefined {
return this.elements.pop();
}
public peek(): Element | undefined {
return this.elements[this.elements.length - 1];
}
}
使用泛型类时,需要为类型参数指定具体类型:
// 创建一个字符串栈
let stringStack = new CustomStack<string>();
stringStack.push("Hello");
stringStack.push("World");
console.info(stringStack.pop()); // 输出: World
// 创建一个数字栈
let numberStack = new CustomStack<number>();
numberStack.push(1);
numberStack.push(2);
console.info(numberStack.pop()); // 输出: 2
编译器会确保类型安全,防止错误操作:
let stringStack = new CustomStack<string>();
stringStack.push(42); // 编译错误:类型'number'不能赋值给类型'string'
🔸 泛型接口
接口也可以是泛型的,用法与泛型类类似:
interface Pair<T, U> {
first: T;
second: U;
}
// 使用泛型接口
let pair: Pair<string, number> = {
first: "Age",
second: 25
};
console.info(pair.first); // 输出: Age
console.info(pair.second); // 输出: 25
📌 泛型约束:限制类型参数
有时我们需要限制泛型类型参数必须具有某些特定的属性或方法。这时可以使用extends
关键字来添加约束。
// 定义一个具有hash方法的接口
interface Hashable {
hash(): number;
}
// 创建一个泛型类,其中Key类型必须实现Hashable接口
class MyHashMap<Key extends Hashable, Value> {
private items: { key: Key; value: Value }[] = [];
public set(k: Key, v: Value): void {
const hash = k.hash();
// 使用hash值进行存储操作
this.items.push({ key: k, value: v });
}
public get(k: Key): Value | undefined {
const hash = k.hash();
// 使用hash值查找对应的值
const item = this.items.find(item => item.key.hash() === hash);
return item?.value;
}
}
使用这个泛型类:
// 创建一个实现了Hashable接口的类
class User implements Hashable {
constructor(public id: number, public name: string) {}
hash(): number {
return this.id;
}
}
// 创建一个用户-分数映射
let userScores = new MyHashMap<User, number>();
// 添加用户和分数
const user1 = new User(1, "Alice");
const user2 = new User(2, "Bob");
userScores.set(user1, 95);
userScores.set(user2, 88);
// 获取用户分数
console.info(userScores.get(user1)); // 输出: 95
📌 泛型函数:编写通用算法
泛型函数允许我们编写可以处理多种类型的函数。
🔸 基本泛型函数
以下是一个返回数组最后一个元素的函数:
// 非泛型版本:只能处理数字数组
function lastNumber(x: number[]): number {
return x[x.length - 1];
}
// 泛型版本:可以处理任何类型的数组
function last<T>(x: T[]): T {
return x[x.length - 1];
}
使用泛型函数:
// 处理数字数组
let lastNum = last<number>([1, 2, 3]);
console.info(lastNum); // 输出: 3
// 处理字符串数组
let lastStr = last<string>(["a", "b", "c"]);
console.info(lastStr); // 输出: c
// 处理对象数组
interface Person {
name: string;
age: number;
}
const people: Person[] = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 }
];
let lastPerson = last<Person>(people);
console.info(lastPerson.name); // 输出: Bob
🔸 类型推断
在大多数情况下,TypeScript可以自动推断泛型类型参数,无需显式指定:
// 显式指定类型参数
let result1 = last<string>(["a", "b", "c"]);
// 隐式类型推断(编译器根据参数类型自动推断)
let result2 = last(["a", "b", "c"]); // 类型推断为string
let result3 = last([1, 2, 3]); // 类型推断为number
🔸 多类型参数
泛型函数可以有多个类型参数:
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
const result = swap<string, number>(["hello", 42]);
console.info(result); // 输出: [42, "hello"]
📌 泛型默认值:简化类型参数
泛型类型的类型参数可以设置默认值,这样在使用时如果不指定具体类型,就会使用默认类型:
// 带默认类型参数的接口
interface Container<T = string> {
value: T;
}
// 不指定类型参数,使用默认类型string
let container1: Container = { value: "hello" };
// 显式指定类型参数为number
let container2: Container<number> = { value: 42 };
泛型函数也可以有默认类型参数:
function createArray<T = string>(length: number, defaultValue: T): T[] {
return new Array<T>(length).fill(defaultValue);
}
// 使用默认类型string
let stringArray = createArray(3, "hello");
console.info(stringArray); // 输出: ["hello", "hello", "hello"]
// 显式指定类型为number
let numberArray = createArray<number>(3, 0);
console.info(numberArray); // 输出: [0, 0, 0]
📌 空安全:处理null和undefined
ArkTS默认采用严格的空值检查,所有类型默认都不允许为null或undefined。
🔸 联合类型与空值
如果变量可能为null,需要使用联合类型:
// 编译错误:不能将null赋值给number类型
let x: number = null;
// 正确:使用联合类型
let y: number | null = null;
y = 42; // 可以赋值为number
y = null; // 也可以赋值为null
🔸 非空断言运算符(!)
非空断言运算符(!)用于告诉编译器某个值不会为null或undefined:
class User {
name: string = "";
}
function processUser(user: User | null) {
// 编译错误:user可能为null
// console.info(user.name);
// 使用非空断言运算符
console.info(user!.name); // 告诉编译器user不会为null
// 更安全的方式是使用条件检查
if (user !== null) {
console.info(user.name);
}
}
⚠️ 注意:非空断言运算符应谨慎使用,如果运行时值确实为null,会导致运行时错误。
🔸 空值合并运算符(??)
空值合并运算符(??)用于提供默认值,当左侧表达式为null或undefined时返回右侧表达式:
class Person {
nickname: string | null = null;
getNickname(): string {
// 如果nickname为null或undefined,返回空字符串
return this.nickname ?? "未设置昵称";
}
}
let person = new Person();
console.info(person.getNickname()); // 输出: 未设置昵称
person.nickname = "小明";
console.info(person.getNickname()); // 输出: 小明
🔸 可选链运算符(?.)
可选链运算符(?.)用于安全地访问可能为null或undefined的对象的属性:
class Person {
name: string;
spouse?: Person; // 可选属性,可能为undefined
constructor(name: string) {
this.name = name;
}
}
let alice = new Person("Alice");
let bob = new Person("Bob");
// 设置Alice的配偶为Bob
alice.spouse = bob;
// 安全访问配偶的名字
console.info(alice.spouse?.name); // 输出: Bob
// Charlie没有配偶
let charlie = new Person("Charlie");
console.info(charlie.spouse?.name); // 输出: undefined,不会抛出错误
可选链可以任意长,包含多个?.运算符:
class Department {
name: string;
manager?: Person;
constructor(name: string) {
this.name = name;
}
}
class Person {
name: string;
department?: Department;
constructor(name: string) {
this.name = name;
}
}
let marketing = new Department("Marketing");
let alice = new Person("Alice");
marketing.manager = alice;
alice.department = marketing;
// 链式访问可能为null/undefined的属性
console.info(marketing.manager?.department?.name); // 输出: Marketing
💡 泛型实战:常见应用场景
🔸 泛型组件
在UI开发中,泛型可以用来创建可复用的组件:
class DataList<T> {
items: T[] = [];
add(item: T): void {
this.items.push(item);
}
getItems(): T[] {
return this.items;
}
render(formatter: (item: T) => string): void {
this.items.forEach(item => {
console.info(formatter(item));
});
}
}
// 使用泛型组件
interface Product {
id: number;
name: string;
price: number;
}
const productList = new DataList<Product>();
productList.add({ id: 1, name: "手机", price: 3999 });
productList.add({ id: 2, name: "笔记本", price: 6999 });
productList.render(product => `${product.name}: ¥${product.price}`);
// 输出:
// 手机: ¥3999
// 笔记本: ¥6999
🔸 泛型工具函数
创建通用的工具函数,适用于多种数据类型:
// 过滤数组元素
function filter<T>(array: T[], predicate: (item: T) => boolean): T[] {
return array.filter(predicate);
}
// 映射数组元素
function map<T, U>(array: T[], mapper: (item: T) => U): U[] {
return array.map(mapper);
}
// 查找数组元素
function find<T>(array: T[], predicate: (item: T) => boolean): T | undefined {
return array.find(predicate);
}
// 使用这些工具函数
const numbers = [1, 2, 3, 4, 5];
const evenNumbers = filter(numbers, n => n % 2 === 0);
console.info(evenNumbers); // 输出: [2, 4]
const doubled = map(numbers, n => n * 2);
console.info(doubled); // 输出: [2, 4, 6, 8, 10]
const found = find(numbers, n => n > 3);
console.info(found); // 输出: 4
🔸 泛型与异步操作
处理异步操作结果:
class Result<T> {
private value?: T;
private error?: Error;
private isSuccess: boolean;
private constructor(isSuccess: boolean, value?: T, error?: Error) {
this.isSuccess = isSuccess;
this.value = value;
this.error = error;
}
static success<U>(value: U): Result<U> {
return new Result<U>(true, value);
}
static failure<U>(error: Error): Result<U> {
return new Result<U>(false, undefined, error);
}
isOk(): boolean {
return this.isSuccess;
}
getValue(): T | undefined {
return this.value;
}
getError(): Error | undefined {
return this.error;
}
}
// 使用Result类处理异步操作
async function fetchData<T>(url: string): Promise<Result<T>> {
try {
// 模拟API调用
const data = { id: 1, name: "测试数据" } as unknown as T;
return Result.success(data);
} catch (error) {
return Result.failure(new Error("获取数据失败"));
}
}
// 使用示例
async function loadUserData() {
interface User {
id: number;
name: string;
}
const result = await fetchData<User>("https://api.example.com/users/1");
if (result.isOk()) {
const user = result.getValue();
console.info(`用户名: ${user?.name}`);
} else {
console.error(`错误: ${result.getError()?.message}`);
}
}
🌈 泛型编程的最佳实践
- 适度使用泛型:泛型很强大,但过度使用会增加代码复杂性
- 提供有意义的类型参数名:使用T、U这样的单字母名称是常见做法,但对于复杂泛型,使用更具描述性的名称(如TElement、TKey)会提高可读性
- 使用泛型约束:通过extends关键字限制类型参数,确保类型安全
- 优先使用类型推断:让编译器自动推断类型参数,减少冗余代码
- 考虑提供默认类型参数:为不常变化的类型参数提供默认值,简化API使用
💫 小结: 泛型是ArkTS中非常强大的特性,掌握它可以让你的代码更加灵活、可复用,同时保持类型安全。结合空安全特性,你可以编写出既灵活又健壮的代码。希望这篇文章对你有所帮助!
❤️ 如果觉得有用,请点赞收藏,你的支持是我持续创作的动力!
#编程学习 #ArkTS #泛型编程 #HarmonyOS #技术干货 #前端开发
更多推荐
所有评论(0)