鸿蒙学习实战之路:ArkTS 泛型编程指南:提升代码复用性与类型安全

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

引言

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

官方参考资料:

泛型基础概念

什么是泛型?

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

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

泛型最佳实践

命名约定

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

  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 应用开发中,掌握泛型编程技巧将帮助你构建更加健壮和灵活的应用程序。

扩展阅读

需要参加鸿蒙认证的请点击 鸿蒙认证链接

posted @ 2025-11-30 23:37  时间煮鱼  阅读(19)  评论(0)    收藏  举报