🚀 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}`);
  }
}

🌈 泛型编程的最佳实践

  1. 适度使用泛型:泛型很强大,但过度使用会增加代码复杂性
  2. 提供有意义的类型参数名:使用T、U这样的单字母名称是常见做法,但对于复杂泛型,使用更具描述性的名称(如TElement、TKey)会提高可读性
  3. 使用泛型约束:通过extends关键字限制类型参数,确保类型安全
  4. 优先使用类型推断:让编译器自动推断类型参数,减少冗余代码
  5. 考虑提供默认类型参数:为不常变化的类型参数提供默认值,简化API使用

💫 小结: 泛型是ArkTS中非常强大的特性,掌握它可以让你的代码更加灵活、可复用,同时保持类型安全。结合空安全特性,你可以编写出既灵活又健壮的代码。希望这篇文章对你有所帮助!

❤️ 如果觉得有用,请点赞收藏,你的支持是我持续创作的动力!

#编程学习 #ArkTS #泛型编程 #HarmonyOS #技术干货 #前端开发

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐