ArkTS 泛型编程指南:提升代码复用性与类型安全

news/2025/11/30 23:39:26/文章来源:https://www.cnblogs.com/Autumnfish/p/19290843

ArkTS 泛型编程指南:提升代码复用性与类型安全

引言

在 HarmonyOS ArkTS 开发中,泛型是一种强大的编程工具,它允许我们编写可重用的代码,同时保持类型安全。通过泛型,我们可以创建适用于多种数据类型的组件、函数和类,而不必为每种类型重复编写相似的代码。本文将深入探讨 ArkTS 中泛型的概念、用法和最佳实践,帮助开发者构建更加灵活和健壮的应用程序。

官方参考资料:

  • HarmonyOS ArkTS 语言参考
  • TypeScript 泛型文档

泛型基础概念

什么是泛型?

泛型是一种编程语言特性,它允许在定义函数、接口或类时不预先指定具体的类型,而在使用时再指定类型的一种特性。这种方式可以让代码更加灵活,提高代码的复用性。

在 ArkTS 中,泛型使用尖括号 <T> 来表示类型参数,其中 T 是一个类型变量,可以在使用时被具体的类型替代。

为什么需要泛型?

在没有泛型的情况下,如果我们想要创建一个适用于多种类型的函数,通常有以下几种方法:

  1. 使用 any 类型:失去类型检查的好处
  2. 为每种类型重载函数:代码重复,维护困难
  3. 使用联合类型:限制了类型的范围

泛型解决了这些问题,它允许我们在保持类型安全的同时编写通用的代码。

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()); // 输出: 3let 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);}
}

泛型最佳实践

命名约定

为了提高代码的可读性,建议遵循以下泛型参数命名约定:

  1. 单个字符的泛型参数用于简单情况:

    • T: 通用类型
    • K: 键类型
    • V: 值类型
    • E: 元素类型
  2. 描述性的泛型参数名用于复杂情况:

    • TItem: 项目类型
    • TUser: 用户类型
    • TProduct: 产品类型

避免过度泛型化

虽然泛型提供了灵活性,但过度使用会使代码变得复杂难以理解。建议:

  • 仅在确实需要处理多种类型时使用泛型
  • 如果一个函数只处理特定类型,不要使用泛型
  • 保持泛型定义简单明了

合理使用泛型约束

使用泛型约束可以提高代码的类型安全性:

  • 总是为泛型参数添加必要的约束,确保它们具有所需的属性和方法
  • 使用接口来定义约束,使代码更加清晰
  • 避免使用过于宽松的约束,如 anyobject

类型推断的使用

利用 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 中一个强大的特性,它允许我们编写更加灵活、可复用且类型安全的代码。通过本文的学习,我们了解了:

  1. 泛型的基本概念和语法
  2. 如何在函数、类、接口中使用泛型
  3. 泛型的高级用法,如约束、默认类型和工具类型
  4. 泛型在组件开发和数据结构实现中的应用
  5. 泛型与异步操作的结合
  6. 泛型编程的最佳实践

合理使用泛型可以显著提高代码的质量和可维护性。在 HarmonyOS ArkTS 应用开发中,掌握泛型编程技巧将帮助你构建更加健壮和灵活的应用程序。

扩展阅读

  • HarmonyOS ArkTS 类型系统
  • TypeScript 泛型深入讲解
  • 设计模式与泛型结合

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/982378.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

87 Windows 系统安装的本质是什么?

Windows 系统安装的本质是什么? 安装 Windows 系统,步骤复杂,方法多样,工具纷繁,变幻莫测.但是万变不离其宗. Windows 系统安装的本质,其实就是两点:解压wim 建立引导解压wim Windows系统安装所需的所有文件都被保存在…

内存溢出问题

内存溢出问题 最近在项目中遇到上传多图片到后端,进行压缩等比缩放,内存溢出的问题

140 Windows 11 新系统一分钟打不开一个软件?原来卡顿的真凶在这里!

Windows 11 新系统一分钟打不开一个软件?原来卡顿的真凶在这里! 前两天安装Windows11虚拟机,发现新系统竟然连一个geek都需要等待1分钟才能打开.这在Windows10中秒开的软件,为什么在Windows11中变得唯唯诺诺?幕后真凶…

124 禁用Windows更新有bug?不如先暂停它2000年!

禁用Windows更新有bug?不如先暂停它2000年! 你是否同样被Windows更新困扰?明明好端端的系统,更新后C盘爆满,出现新的bug,更新时间还长,更可怕的是还可能出现更新失败的情况,直接导致系统崩溃,重要的数据丢失.每天看见…

43 微软官方安装Windows途径

微软官方安装Windows途径 和大家讲了这么多,好像从来没讲过微软希望我们用什么方法安装Windows系统.(偷笑) 那今天就浅浅的介绍一下微软官方提供的安装Windows系统的方法吧! 1.原系统为Windows7及以下: 下载Windows10的…

82 深入解析 Windows RE:系统维护的强大工具

深入解析 Windows RE:系统维护的强大工具 在日常使用 Windows 的过程中, 用户难免会遇到系统启动故障、无法正常操作等问题.这时, 一个熟悉又陌生的工具——Windows Recovery Environment (Windows RE)——往往能够扭转…

126 激活Windows系统的四种终极方法

激活Windows系统的四种终极方法 今天讨论一个敏感话题,如何破解激活Windows系统? 要知道,我们可是从来不用正版的 : ) 我们在网上搜索Windows激活,可以搜出一大堆东西.那么,这么多眼花缭乱的激活方法,有没有一个鼻祖呢…

99 如何破解 Windows 系统密码?

如何破解 Windows 系统密码?请注意:本文不是教非法的事情.而是教实用技术.Windows系统开机密码忘了,怎么办? 下面说说一般情况下Windows系统登录的时候都有哪些情况:使用本地账户登录 使用微软账户登录无论使用本地账…

讲讲django的文件对象

一、django文件对象是什么? Django 抽象出了一套“文件处理体系”,核心是:Django 所有文件类的基类 提供统一的接口用于:读取写入迭代存储到 Storage 后端(本地/OSS/S3)使用FileField / ImageField来构造:class…

香港中文大学(深圳) PHY1001 - Mechanics 笔记

笔记包括2025年秋学期该课程的知识点笔记包括2025年秋学期该课程的知识点 不包含高中物理中有的一些常见知识点Idealized Models 理想模型 为了研究最重要的性质而建立的比现实简单的模型,如研究物体下落轨迹时会忽略…

P9606 ABB

点击查看代码 #include<bits/stdc++.h>using namespace std;const int N=8e5+10; char str[N]; int ne[N];int main() {int n;string s;cin>>n>>s;//构造反转拼接字符串for(int i=0;i<n;i++){str…

微PE的磁盘化启动:不再使用WEPE64.WIM,直接从分区启动PE系统!

微PE的磁盘化启动:不再使用WEPE64.WIM,直接从分区启动PE系统 我们曾经学习过,PE系统的启动模式是Ramdisk.这是一种通过将内存虚拟成一块磁盘分区的方式启动的模式. (具体内容详见我的第25篇文章:《25Ramdisk 启动模式简…

73 Windows系统磁盘与分区知识详解

Windows系统磁盘与分区知识详解 在日常使用Windows操作系统的过程中,我们常常会接触到磁盘管理,磁盘分区等操作.然而,许多人可能并不完全理解磁盘和分区的运作原理以及如何高效管理它们. 本篇文章将探讨Windows系统中关…

不说废话,硬核干货:重装系统方法大总结!

不说废话,硬核干货:重装系统方法大总结! 重装系统到底有多少种方法?每种方法都怎么操作?本文列举出所有可行的重装系统方法,不讲原理,就是实操! 任何新系统的产生,都是依赖于它之前的系统.使用ISO直接装系统 我们从微…

isnumeric() 和 isdigit() 的区别

在 Python 中,isnumeric() 和 isdigit() 都是字符串方法,用于检查字符串中的字符是否属于特定类别(如数字)。虽然它们功能相似,但在处理某些字符时存在区别。以下是主要差异和适用场景:1. isdigit()定义:检查字…

109 C盘又变红了?清理C盘的便捷方法!

C盘又变红了?清理C盘的便捷方法! C 盘空间不足,是大多数 Windows 用户的“常见病”。当系统盘告急,不仅影响软件安装和系统更新,还可能拖慢整机速度。今天,我们就以“细致、系统、易上手”为原则全面清理 C 盘,让…

79 对系统迁移几种方案的经验之谈

对系统迁移几种方案的经验之谈声明: 本文由@CDsidi大佬所作,感谢CDsidi大佬为公众号贡献技术文章!!!最近笔记本电脑硬盘空间不够用了,于是打算换一个新硬盘,把原来的旧硬盘换下来.为了保持使用习惯的连续性,决定采用全…

108 彻底卸载Windows Defender的开源小工具

彻底卸载Windows Defender的开源小工具 在 Windows 系统中,内置的 Windows Defender(微软安全中心)虽然能够提供基本防护,但在某些场景下会影响性能或与第三方安全/虚拟化软件产生冲突。对于需要彻底移除或禁用 De…

梅德乐:从愿景到实践的十七载坚守

在广州番禺区的某栋科研楼内,凌晨两点仍亮着灯的实验室里,梅德乐的科研团队正为一位县级医院内科医师的论文做最后的数据验证。这是梅德乐十七年来的日常缩影:从2009年成立至今,这家专注于生物医学科研转化的企业,…

【UEFI实战】在库中使用全局变量

说明 本文涉及的代码都可以在vUDK2017: https://github.com/tianocore/edk2.git Tag vUDK2017.中找到。 一个不怎么好的测试代码 有两个驱动,NullDxeDriverOne.inf和NullDxeDriverTwo.inf,它们做的事情只有一件,就是…