鸿蒙学习实战之路:ArkTS 泛型编程指南:提升代码复用性与类型安全
ArkTS 泛型编程指南:提升代码复用性与类型安全
引言
在 HarmonyOS ArkTS 开发中,泛型是一种强大的编程工具,它允许我们编写可重用的代码,同时保持类型安全。通过泛型,我们可以创建适用于多种数据类型的组件、函数和类,而不必为每种类型重复编写相似的代码。本文将深入探讨 ArkTS 中泛型的概念、用法和最佳实践,帮助开发者构建更加灵活和健壮的应用程序。
官方参考资料:
泛型基础概念
什么是泛型?
泛型是一种编程语言特性,它允许在定义函数、接口或类时不预先指定具体的类型,而在使用时再指定类型的一种特性。这种方式可以让代码更加灵活,提高代码的复用性。
在 ArkTS 中,泛型使用尖括号 <T> 来表示类型参数,其中 T 是一个类型变量,可以在使用时被具体的类型替代。
为什么需要泛型?
在没有泛型的情况下,如果我们想要创建一个适用于多种类型的函数,通常有以下几种方法:
- 使用
any类型:失去类型检查的好处 - 为每种类型重载函数:代码重复,维护困难
- 使用联合类型:限制了类型的范围
泛型解决了这些问题,它允许我们在保持类型安全的同时编写通用的代码。
ArkTS 泛型语法
泛型函数
泛型函数是最基本的泛型用法,它允许我们创建可以处理多种类型数据的函数。
// 泛型函数基本语法
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型函数
let output1 = identity<string>("Hello ArkTS"); // 明确指定类型
let output2 = identity(42); // 类型推断
泛型类
泛型类允许我们在类的定义中使用类型参数,使得类可以处理不同类型的数据。
// 泛型类基本语法
class GenericClass<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
setValue(value: T): void {
this.value = value;
}
}
// 使用泛型类
let numberClass = new GenericClass<number>(100);
let stringClass = new GenericClass<string>("Hello");
泛型接口
泛型接口定义了可以接受类型参数的接口,使得接口更加灵活。
// 泛型接口基本语法
interface GenericInterface<T> {
data: T;
getData(): T;
}
// 实现泛型接口
class StringImpl implements GenericInterface<string> {
data: string;
constructor(data: string) {
this.data = data;
}
getData(): string {
return this.data;
}
}
泛型的高级用法
多个类型参数
ArkTS 支持在一个泛型定义中使用多个类型参数,用逗号分隔。
// 多个类型参数的泛型函数
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
// 使用
let result = pair<string, number>("count", 42);
泛型约束
泛型约束用于限制泛型参数必须满足的条件,确保泛型类型具有某些特定的属性或方法。
// 定义一个接口作为约束
interface Lengthwise {
length: number;
}
// 使用约束的泛型函数
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // 现在我们知道它有一个 .length 属性,所以不会报错
return arg;
}
// 有效使用
loggingIdentity("Hello"); // 字符串有 length 属性
loggingIdentity([1, 2, 3]); // 数组有 length 属性
// 无效使用
// loggingIdentity(42); // 数字没有 length 属性,会报错
泛型默认类型
在 ArkTS 中,我们可以为泛型参数指定默认类型,这样在不明确指定类型时会使用默认类型。
// 带有默认类型的泛型函数
function createArray<T = number>(length: number, value: T): T[] {
return Array(length).fill(value);
}
// 使用默认类型
let numbers = createArray(5, 0); // number[]
// 明确指定类型
let strings = createArray<string>(3, "default"); // string[]
泛型工具类型
ArkTS 提供了一些内置的泛型工具类型,用于常见的类型转换操作。
Partial
将类型 T 的所有属性变为可选。
interface User {
id: number;
name: string;
age: number;
}
// 所有属性变为可选
let partialUser: Partial<User> = { name: "张三" };
Required
将类型 T 的所有属性变为必需。
interface PartialConfig {
host?: string;
port?: number;
}
// 所有属性变为必需
let requiredConfig: Required<PartialConfig> = {
host: "localhost",
port: 8080,
};
Readonly
将类型 T 的所有属性变为只读。
interface Config {
apiKey: string;
timeout: number;
}
// 所有属性变为只读
let readonlyConfig: Readonly<Config> = {
apiKey: "abc123",
timeout: 5000,
};
// 尝试修改会报错
// readonlyConfig.timeout = 10000; // 错误
Record<K, T>
创建一个类型,其属性键为 K,属性值为 T。
// 创建一个字符串键、数字值的对象类型
let scores: Record<string, number> = {
Alice: 95,
Bob: 87,
Charlie: 92,
};
Pick<T, K>
从类型 T 中选择一组属性 K。
interface Person {
id: number;
name: string;
age: number;
address: string;
}
// 只选择 name 和 age 属性
let personInfo: Pick<Person, "name" | "age"> = {
name: "李四",
age: 25,
};
Omit<T, K>
从类型 T 中排除一组属性 K。
// 排除 id 和 address 属性
let personBasic: Omit<Person, "id" | "address"> = {
name: "王五",
age: 30,
};
ArkTS 中的泛型组件
泛型组件基础
在 ArkTS 中,我们可以创建接受类型参数的组件,使其能够适应不同类型的数据。
@Component
struct GenericList<T> {
@State items: T[] = [];
build() {
Column() {
ForEach(this.items, (item: T, index: number) => {
Text(`Item ${index}: ${JSON.stringify(item)}`)
.fontSize(16)
.margin(10)
}, (item: T, index: number) => JSON.stringify(item) + index)
}
}
}
// 使用泛型组件
@Component
struct GenericComponentDemo {
build() {
Column() {
GenericList<string>({ items: ["Apple", "Banana", "Orange"] })
GenericList<number>({ items: [1, 2, 3, 4, 5] })
}
}
}
带约束的泛型组件
我们可以为泛型组件添加约束,确保传入的类型满足特定条件。
interface Displayable {
name: string;
toString(): string;
}
@Component
struct DisplayList<T extends Displayable> {
@State items: T[] = [];
build() {
Column() {
ForEach(this.items, (item: T, index: number) => {
Text(`${index + 1}. ${item.name}`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin(8)
}, (item: T, index: number) => item.name + index)
}
}
}
// 使用带约束的泛型组件
class Product implements Displayable {
name: string;
price: number;
constructor(name: string, price: number) {
this.name = name;
this.price = price;
}
toString(): string {
return `${this.name} - ¥${this.price}`;
}
}
@Component
struct ProductList {
build() {
DisplayList<Product>({
items: [
new Product("手机", 3999),
new Product("电脑", 7999),
new Product("平板", 2999)
]
})
}
}
泛型组件中的事件处理
泛型组件可以定义泛型事件,使事件回调能够处理特定类型的数据。
@Component
struct SelectableList<T> {
@State items: T[] = [];
onSelect: (item: T) => void;
build() {
Column() {
ForEach(this.items, (item: T, index: number) => {
Text(JSON.stringify(item))
.fontSize(16)
.margin(10)
.padding(10)
.backgroundColor(0xF0F0F0)
.onClick(() => {
this.onSelect(item);
})
}, (item: T, index: number) => JSON.stringify(item) + index)
}
}
}
@Component
struct EventDemo {
@State selectedItem: any = null;
build() {
Column() {
SelectableList<{ id: number; name: string }>({
items: [
{ id: 1, name: "选项1" },
{ id: 2, name: "选项2" },
{ id: 3, name: "选项3" }
],
onSelect: (item) => {
this.selectedItem = item;
console.log(`选中了: ${item.name}`);
}
})
if (this.selectedItem) {
Text(`当前选中: ${this.selectedItem.name}`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.marginTop(20)
}
}
}
}
泛型在数据结构中的应用
泛型栈实现
使用泛型实现一个栈数据结构,可以存储任意类型的元素。
class Stack<T> {
private items: T[] = [];
// 添加元素到栈顶
push(item: T): void {
this.items.push(item);
}
// 移除并返回栈顶元素
pop(): T | undefined {
return this.items.pop();
}
// 返回栈顶元素但不移除
peek(): T | undefined {
return this.items[this.items.length - 1];
}
// 判断栈是否为空
isEmpty(): boolean {
return this.items.length === 0;
}
// 获取栈的大小
size(): number {
return this.items.length;
}
// 清空栈
clear(): void {
this.items = [];
}
}
// 使用泛型栈
let numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);
console.log(numberStack.pop()); // 输出: 3
let stringStack = new Stack<string>();
stringStack.push("Hello");
stringStack.push("ArkTS");
console.log(stringStack.peek()); // 输出: ArkTS
泛型队列实现
使用泛型实现一个队列数据结构。
class Queue<T> {
private items: T[] = [];
// 入队
enqueue(item: T): void {
this.items.push(item);
}
// 出队
dequeue(): T | undefined {
return this.items.shift();
}
// 返回队首元素
front(): T | undefined {
return this.items[0];
}
// 判断队列是否为空
isEmpty(): boolean {
return this.items.length === 0;
}
// 获取队列大小
size(): number {
return this.items.length;
}
// 清空队列
clear(): void {
this.items = [];
}
}
// 使用泛型队列
let messageQueue = new Queue<{ id: number; content: string }>();
messageQueue.enqueue({ id: 1, content: "消息1" });
messageQueue.enqueue({ id: 2, content: "消息2" });
console.log(messageQueue.dequeue()?.content); // 输出: 消息1
泛型映射实现
使用泛型实现一个简单的映射(字典)数据结构。
class GenericMap<K, V> {
private map: Map<K, V> = new Map<K, V>();
// 添加键值对
set(key: K, value: V): void {
this.map.set(key, value);
}
// 获取值
get(key: K): V | undefined {
return this.map.get(key);
}
// 删除键值对
delete(key: K): boolean {
return this.map.delete(key);
}
// 判断键是否存在
has(key: K): boolean {
return this.map.has(key);
}
// 获取所有键
keys(): IterableIterator<K> {
return this.map.keys();
}
// 获取所有值
values(): IterableIterator<V> {
return this.map.values();
}
// 获取所有键值对
entries(): IterableIterator<[K, V]> {
return this.map.entries();
}
// 获取映射大小
size(): number {
return this.map.size;
}
// 清空映射
clear(): void {
this.map.clear();
}
}
// 使用泛型映射
let userMap = new GenericMap<string, { name: string; age: number }>();
userMap.set("user1", { name: "张三", age: 28 });
userMap.set("user2", { name: "李四", age: 32 });
console.log(userMap.get("user1")?.name); // 输出: 张三
泛型与异步操作
泛型 Promise 处理
泛型可以与 Promise 结合使用,使异步操作的返回类型更加明确。
// 定义返回特定类型的异步函数
async function fetchData<T>(url: string): Promise<T> {
// 模拟网络请求
return new Promise<T>((resolve, reject) => {
setTimeout(() => {
// 模拟返回数据
resolve({} as T);
}, 1000);
});
}
// 定义数据接口
interface UserData {
id: number;
name: string;
email: string;
}
// 使用泛型异步函数
async function processUserData() {
try {
const userData: UserData = await fetchData<UserData>(
"https://api.example.com/user"
);
console.log(userData.name); // 类型安全
} catch (error) {
console.error("获取数据失败:", error);
}
}
泛型与 Future/Promise 工具函数
创建泛型工具函数来处理 Promise 操作,提高代码的复用性。
// 泛型工具函数:延迟执行
function delay<T>(ms: number, value: T): Promise<T> {
return new Promise((resolve) => {
setTimeout(() => resolve(value), ms);
});
}
// 泛型工具函数:重试异步操作
async function retry<T>(
operation: () => Promise<T>,
maxAttempts: number = 3,
delayMs: number = 1000
): Promise<T> {
let lastError: Error;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
console.log(`尝试 ${attempt} 失败,${delayMs}ms 后重试...`);
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
throw lastError!;
}
// 使用示例
async function fetchWithRetry() {
try {
const result = await retry(() =>
fetchData<{ success: boolean }>("https://api.example.com/retry")
);
console.log("获取成功:", result);
} catch (error) {
console.error("多次尝试后仍然失败:", error);
}
}
泛型最佳实践
命名约定
为了提高代码的可读性,建议遵循以下泛型参数命名约定:
-
单个字符的泛型参数用于简单情况:
T: 通用类型K: 键类型V: 值类型E: 元素类型
-
描述性的泛型参数名用于复杂情况:
TItem: 项目类型TUser: 用户类型TProduct: 产品类型
避免过度泛型化
虽然泛型提供了灵活性,但过度使用会使代码变得复杂难以理解。建议:
- 仅在确实需要处理多种类型时使用泛型
- 如果一个函数只处理特定类型,不要使用泛型
- 保持泛型定义简单明了
合理使用泛型约束
使用泛型约束可以提高代码的类型安全性:
- 总是为泛型参数添加必要的约束,确保它们具有所需的属性和方法
- 使用接口来定义约束,使代码更加清晰
- 避免使用过于宽松的约束,如
any或object
类型推断的使用
利用 ArkTS 的类型推断功能可以简化代码:
// 不必显式指定类型
function identity<T>(arg: T): T {
return arg;
}
// 类型推断,不需要显式指定 string 类型
let result = identity("Hello"); // 类型推断为 string
泛型与具体类型的平衡
在设计 API 时,需要平衡泛型的灵活性和具体类型的明确性:
- 公共 API 通常应该提供具体的类型版本,提高易用性
- 内部工具函数可以使用更多的泛型,提高复用性
- 考虑为复杂的泛型 API 提供更简单的包装器
实际应用案例
通用表单验证器
使用泛型创建一个通用的表单验证器,可以验证不同结构的表单数据。
// 验证规则接口
interface ValidationRule<T> {
validate: (value: T) => boolean;
errorMessage: string;
}
// 泛型表单验证器
class FormValidator<T extends Record<string, any>> {
private rules: Map<keyof T, ValidationRule<any>[]> = new Map();
// 添加验证规则
addRule<K extends keyof T>(field: K, rule: ValidationRule<T[K]>): void {
if (!this.rules.has(field)) {
this.rules.set(field, []);
}
this.rules.get(field)!.push(rule);
}
// 验证表单数据
validate(data: T): { isValid: boolean; errors: Record<string, string[]> } {
const errors: Record<string, string[]> = {};
// 遍历所有规则
this.rules.forEach((fieldRules, field) => {
const value = data[field];
const fieldErrors: string[] = [];
// 验证每个规则
fieldRules.forEach((rule) => {
if (!rule.validate(value)) {
fieldErrors.push(rule.errorMessage);
}
});
if (fieldErrors.length > 0) {
errors[field as string] = fieldErrors;
}
});
return {
isValid: Object.keys(errors).length === 0,
errors,
};
}
}
// 示例:用户注册表单
interface UserRegistration {
username: string;
email: string;
password: string;
}
// 创建验证器
const userValidator = new FormValidator<UserRegistration>();
// 添加验证规则
userValidator.addRule("username", {
validate: (value) => value.length >= 3 && value.length <= 20,
errorMessage: "用户名长度必须在3-20个字符之间",
});
userValidator.addRule("email", {
validate: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
errorMessage: "请输入有效的邮箱地址",
});
userValidator.addRule("password", {
validate: (value) => value.length >= 8,
errorMessage: "密码长度至少为8个字符",
});
// 验证数据
const formData: UserRegistration = {
username: "user123",
email: "invalid-email",
password: "123",
};
const result = userValidator.validate(formData);
console.log(result);
// 输出:
// {
// isValid: false,
// errors: {
// email: ["请输入有效的邮箱地址"],
// password: ["密码长度至少为8个字符"]
// }
// }
通用状态管理
使用泛型创建一个简单的状态管理工具,可以管理不同类型的状态。
// 泛型状态管理类
class StateManager<T> {
private state: T;
private listeners: Set<(state: T) => void> = new Set();
constructor(initialState: T) {
this.state = initialState;
}
// 获取当前状态
getState(): T {
return { ...this.state } as T; // 返回副本避免直接修改
}
// 更新状态
setState(newState: Partial<T>): void {
this.state = { ...this.state, ...newState } as T;
this.notifyListeners();
}
// 替换整个状态
replaceState(newState: T): void {
this.state = newState;
this.notifyListeners();
}
// 添加状态变化监听器
subscribe(listener: (state: T) => void): () => void {
this.listeners.add(listener);
// 立即调用监听器,提供当前状态
listener(this.getState());
// 返回取消订阅函数
return () => {
this.listeners.delete(listener);
};
}
// 通知所有监听器
private notifyListeners(): void {
const stateCopy = this.getState();
this.listeners.forEach((listener) => {
listener(stateCopy);
});
}
}
// 示例:用户状态管理
interface UserState {
isLoggedIn: boolean;
userInfo: { name: string; id: string } | null;
loading: boolean;
}
// 创建状态管理器
const userStateManager = new StateManager<UserState>({
isLoggedIn: false,
userInfo: null,
loading: false,
});
// 订阅状态变化
const unsubscribe = userStateManager.subscribe((state) => {
console.log("用户状态更新:", state);
});
// 更新状态
userStateManager.setState({ loading: true });
// 模拟登录
setTimeout(() => {
userStateManager.setState({
isLoggedIn: true,
userInfo: { name: "张三", id: "user123" },
loading: false,
});
}, 1000);
// 不再需要时取消订阅
// unsubscribe();
总结
泛型是 ArkTS 中一个强大的特性,它允许我们编写更加灵活、可复用且类型安全的代码。通过本文的学习,我们了解了:
- 泛型的基本概念和语法
- 如何在函数、类、接口中使用泛型
- 泛型的高级用法,如约束、默认类型和工具类型
- 泛型在组件开发和数据结构实现中的应用
- 泛型与异步操作的结合
- 泛型编程的最佳实践
合理使用泛型可以显著提高代码的质量和可维护性。在 HarmonyOS ArkTS 应用开发中,掌握泛型编程技巧将帮助你构建更加健壮和灵活的应用程序。
扩展阅读
需要参加鸿蒙认证的请点击 鸿蒙认证链接

浙公网安备 33010602011771号