零基础鸿蒙应用开发第十七节:泛型基础入门与实战应用

零基础鸿蒙应用开发学习计划表

【学习目标】

  1. 理解泛型的核心本质,明确其解决“类型固化、逻辑重复”问题的核心价值;
  2. 掌握泛型函数、泛型接口、泛型约束(extends)的标准语法;
  3. 能基于自定义业务接口(IUser/IContact)落地泛型,实现“一套接口适配多类数据”;
  4. 兼顾泛型的灵活性与类型安全,规避无约束泛型导致的编译/运行时错误。

【学习重点】

  1. 核心本质:用“类型参数<T>”替代固定类型,让函数/接口/闭包适配多类型;
  2. 核心语法:泛型函数<T>、泛型接口interface IGeneric<T>、泛型约束T extends 类型
  3. 核心价值:从根源减少重复代码,兼顾类型复用与静态类型安全;
  4. 实战落地:泛型适配基础类型(number/string)和自定义业务接口(IUser/IContact)。

一、工程结构与基础接口定义

本节我们将创建名为GenericBasicDemo的工程,基于 鸿蒙5.0(API12) 开发,使用 DevEco Studio 6.0+ 工具,项目结构目录如下:

GenericBasicDemo/
├── entry/
│   ├── src/
│   │   ├── main/
│   │   │   ├── ets/
│   │   │   │   ├── entryability/  # 应用入口(默认生成,无需修改)
│   │   │   │   ├── pages/         # 可视化测试页面
│   │   │   │   │   └── Index.ets  
│   │   │   │   ├── interfaces/    # 接口定义目录
│   │   │   │   │   ├── UserInterfaces.ets  # 用户核心接口
│   │   │   │   │   ├── ContactInterfaces.ets # 联系人核心接口
│   │   │   │   │   └── GenericInterfaces.ets # 泛型接口专属定义
│   │   │   │   └── utils/         # 泛型核心逻辑封装
│   │   │   │       └── GenericTest.ets
│   │   │   ├── resources/         # 资源文件(默认生成)
│   │   │   └── module.json5       # 工程配置(默认生成)

1.1 基础用户接口(UserInterfaces.ets)

// interfaces/UserInterfaces.ets
/**
 * 用户信息核心接口(用于泛型适配实战)
 */
export interface IUser {
  id: number;         // 用户唯一标识(必选)
  name: string;       // 用户名(必选)
  age: number;        // 年龄(必选)
  printInfo: () => void; // 打印用户信息的方法(必选)
}

1.2 联系人接口(ContactInterfaces.ets)

// interfaces/ContactInterfaces.ets
/**
 * 联系人核心接口(泛型实战用)
 */
export interface IContact {
  id: number;         // 联系人唯一ID(必选)
  name: string;       // 联系人姓名(必选)
  phone: string;      // 联系电话(必选)
  email?: string;     // 可选:邮箱
  getContactInfo: () => string; // 获取联系人信息的方法(必选)
}

二、泛型:解决“类型固化”的核心方案

2.1 为什么需要泛型?

前序学习中,非泛型代码存在明显痛点:相同逻辑需适配不同类型时,只能重复编写函数/接口,导致代码冗余、维护成本高。

以数据筛选为例,非泛型写法需要为不同类型重复造轮子:

// utils/GenericTest.ets(非泛型反例)
import { IUser } from '../interfaces/UserInterfaces';
import { IContact } from '../interfaces/ContactInterfaces';

// 1. 仅适配数字数组的筛选函数
function filterNumberArr(arr: number[], condition: (item: number) => boolean): number[] {
  return arr.filter(condition);
}

// 2. 仅适配IUser数组的筛选函数(逻辑与上面完全一致,仅类型不同)
function filterUserArr(arr: IUser[], condition: (item: IUser) => boolean): IUser[] {
  return arr.filter(condition);
}

// 3. 仅适配IContact数组的筛选函数(重复逻辑)
function filterContactArr(arr: IContact[], condition: (item: IContact) => boolean): IContact[] {
  return arr.filter(condition);
}

泛型的核心价值:用一份逻辑适配所有类型,彻底消除重复代码,同时保留ArkTS的静态类型安全。

2.2 泛型核心定义

泛型(Generic)是ArkTS中“类型参数化”的语法,核心逻辑:

  • 定义函数/接口时,用类型参数(如<T>T为类型占位符,可自定义名称)替代固定类型;
  • 使用时指定具体类型(如<number>/<IUser>/<IContact>),或让编译器自动推导类型;
  • 编译器会根据指定类型,自动生成对应类型的约束逻辑,避免类型错误。

三、泛型核心语法

3.1 泛型运算函数

实现支持数值加法、字符串拼接的泛型函数,通过严格的类型守卫保证参数类型一致:

// utils/GenericTest.ets
/**
 * 泛型运算函数:支持数值加法、字符串拼接
 * 核心:通过精准类型守卫拆分number/string逻辑,让编译器明确类型
 * @param a 第一个参数(仅number/string)
 * @param b 第二个参数(需与a类型一致)
 * @returns 运算结果(与输入类型一致)
 */
function combine<T extends number | string>(a: T, b: T): T {
  // 严格类型守卫:确保两个参数类型完全一致
  if (typeof a !== typeof b) {
    throw new Error("参数类型必须一致(仅支持number或string)");
  }

  // 拆分number/string运算逻辑
  if (typeof a === 'number' && typeof b === 'number') {
    return (a + b) as T; // 明确number类型加法
  } else if (typeof a === 'string' && typeof b === 'string') {
    return (a + b) as T; // 明确string类型拼接
  }
  // 兜底(理论上不会执行,因已约束T的范围+类型一致检查)
  throw new Error("仅支持number或string类型的参数");
}

export function combineTest() {
  // 测试调用(合法场景)
  const numResult = combine(10, 20); // T=number,编译通过
  console.log(`数值运算结果:${numResult}`); // 输出:30

  const strResult = combine("Hello, ", "ArkTS"); // T=string,编译通过
  console.log(`字符串运算结果:${strResult}`); // 输出:"Hello, ArkTS"

  // 错误示例:显式指定number,但传入字符串(编译报错)
  // const errorResult1 = combine<number>(10, "20"); 
}

3.2 通用筛选函数(适配任意类型数组)

实现一份筛选逻辑,同时适配基础类型、IUserIContact

// utils/GenericTest.ets
import { IUser } from '../interfaces/UserInterfaces';
import { IContact } from '../interfaces/ContactInterfaces';

/**
 * 泛型筛选函数:一份逻辑适配所有类型数组
 * @param arr 任意类型数组(由泛型T指定)
 * @param condition 筛选条件函数(参数类型与数组元素一致)
 * @returns 同类型筛选结果数组
 */
function filterArr<T>(arr: T[], condition: (item: T) => boolean): T[] {
  return arr.filter(condition);
}

// 测试数据初始化
const numArr = [1, 2, 3, 4, 5];
const userArr: IUser[] = [
  { 
    id: 1, name: "张三", age: 18, 
    printInfo: () => { console.log(`用户名:张三,年龄:18`); } 
  },
  { 
    id: 2, name: "李四", age: 25, 
    printInfo: () => { console.log(`用户名:李四,年龄:25`); } 
  },
  { 
    id: 3, name: "王五", age: 17, 
    printInfo: () => { console.log(`用户名:王五,年龄:17`); } 
  }
];
const contactArr: IContact[] = [
  { 
    id: 101, name: "王麻子", phone: "13800138000", email: "wang@example.com",
    getContactInfo: () => `姓名:王麻子,电话:13800138000` 
  },
  { 
    id: 102, name: "李小花", phone: "13900139000",
    getContactInfo: () => `姓名:李小花,电话:13900139000` 
  },
  { 
    id: 103, name: "张小红", phone: "13700137000", email: "zhang@example.com",
    getContactInfo: () => `姓名:张小红,电话:13700137000` 
  }
];

export function filterArrTest() {
  // 1. 筛选数字数组(T=number)
  const evenNums = filterArr<number>(numArr, (item) => item % 2 === 0);
  console.log(`筛选偶数:${evenNums}`); // 输出:[2,4]

  // 2. 筛选用户数组(T=IUser)
  const adultUsers = filterArr(userArr, (item) => item.age >= 18);
  console.log(`成年用户:${adultUsers.map(u => u.name)}`); // 输出:["张三","李四"]

  // 3. 筛选联系人数组(T=IContact)
  const hasEmailContacts = filterArr(contactArr, (item) => !!item.email);
  console.log(`有邮箱的联系人:${hasEmailContacts.map(c => c.name)}`); // 输出:["王麻子","张小红"]
}

3.3 泛型约束(extends关键字)

通过extends限定泛型范围,实现“限定对象类型的筛选函数”:

// utils/GenericTest.ets
/**
 * 改造泛型筛选函数:仅适配对象类型数组(排除基础类型)
 * @param arr 对象类型数组(由泛型T指定)
 * @param condition 筛选条件函数(参数类型与数组元素一致)
 * @returns 同类型筛选结果数组
 */
function filterObjArr<T extends object>(arr: T[], condition: (item: T) => boolean): T[] {
  return arr.filter(condition);
}

export function filterObjArrTest() {
  // 约束效果示例:基础类型数组会编译报错,对象类型数组正常
  // const numErr = filterObjArr(numArr, item => item % 2 === 0); // 报错:number不满足object约束
  const adultUserObj = filterObjArr(userArr, item => item.age >= 18); // 正常:IUser是object类型
  console.log(`泛型约束筛选成年用户:${adultUserObj.map(u => u.name)}`); // 输出:["张三","李四"]
}

3.4 泛型列表接口(GenericInterfaces.ets)

// interfaces/GenericInterfaces.ets
/**
 * 泛型列表接口:适配任意类型的列表数据(用户列表、联系人列表等)
 */
export interface IListData<T> {
  total: number; // 列表总数(必选)
  list: T[];     // 列表数据(任意类型数组,由泛型T指定)
  getFirst: () => T | undefined; // 获取列表第一条数据
}

3.5 泛型接口实战(多业务类型适配)

复用IListData<T>泛型接口,分别适配IUserIContact列表:

// utils/GenericTest.ets
import { IListData } from '../interfaces/GenericInterfaces';

/**
 * 初始化用户列表(复用IListData<IUser>)
 */
function initUserList(): IListData<IUser> {
  const list: IUser[] = [
    {
      id: 1, name: "张三", age: 18,
      printInfo: () => console.log(`用户名:张三,年龄:18`)
    },
    {
      id: 2, name: "李四", age: 25,
      printInfo: () => console.log(`用户名:李四,年龄:25`)
    }
  ];
  return {
    total: 2,
    list,
    getFirst: () => list[0] // 改用局部变量list,避免this指向错误
  };
}

/**
 * 初始化联系人列表(复用IListData<IContact>)
 * 核心:无需重新定义列表接口,仅指定泛型类型即可
 */
function initContactList(): IListData<IContact> {
  const list: IContact[] = [
    {
      id: 101, name: "王麻子", phone: "13800138000", email: "wang@example.com",
      getContactInfo: () => `姓名:王麻子,电话:13800138000`
    },
    {
      id: 102, name: "李小花", phone: "13900139000",
      getContactInfo: () => `姓名:李小花,电话:13900139000`
    },
    {
      id: 103, name: "张小红", phone: "13700137000", email: "zhang@example.com",
      getContactInfo: () => `姓名:张小红,电话:13700137000`
    }
  ];
  return {
    total: 3,
    list,
    getFirst: () => list[0] // 改用局部变量list,避免this指向错误
  };
}

export function listTest() {
  // 测试调用
  const userList = initUserList();
  console.log(`用户列表第一条:${userList.getFirst()?.name}`); // 输出:"张三"

  const contactList = initContactList();
  console.log(`联系人列表第一条:${contactList.getFirst()?.getContactInfo()}`); 
  // 输出:"姓名:王麻子,电话:13800138000"
}

3.6 泛型闭包(多类型计数器)

结合闭包知识,实现适配number/string的泛型计数器:

// utils/GenericTest.ets
/**
 * 泛型计数器闭包:适配数值/字符串前缀计数
 * @param prefix 字符串前缀(仅当T为string时生效)
 * @returns 计数函数(返回值类型与泛型T一致)
 */
function createGenericCounter<T extends number | string>(prefix?: string): () => T {
  let count: number = 0;

  return (): T => {
    count++;
    // 根据泛型类型返回对应结果
    if (prefix && typeof prefix === 'string') {
      return `${prefix}-${count}` as T;
    }
    return count as T;
  };
}

export function counterTest() {
  // 1. 数值计数器(T=number)
  const numCounter = createGenericCounter<number>();
  console.log(`数值计数1:${numCounter()}`); // 输出:1
  console.log(`数值计数2:${numCounter()}`); // 输出:2

  // 2. 字符串前缀计数器(T=string)
  const orderCounter = createGenericCounter<string>("订单");
  console.log(`订单计数1:${orderCounter()}`); // 输出:"订单-1"
  console.log(`订单计数2:${orderCounter()}`); // 输出:"订单-2"
}

3.7 常见错误提示

错误1:泛型约束不满足

// 错误示例:基础类型数组传入仅支持对象类型的筛选函数
const numErr = filterObjArr(numArr, item => item % 2 === 0); 
// 编译报错:Argument of type 'number[]' is not assignable to parameter of type 'object[]'
// 原因:T extends object 限定泛型必须是对象类型,number不满足约束

错误2:泛型类型与实际参数不匹配

// 错误示例:显式指定T为number,但传入string参数
const errorResult = combine<number>(10, "20");
// 编译报错:Argument of type 'string' is not assignable to parameter of type 'number'
// 原因:泛型类型与参数类型必须严格一致

四、可视化测试页面(Index.ets)

整合所有泛型实战逻辑,便于可视化调试验证:

// pages/Index.ets
import {
  combineTest,
  filterArrTest,
  filterObjArrTest,
  listTest,
  counterTest
} from '../utils/GenericTest';

@Entry
@Component
struct GenericIndex {
  build() {
    Column({ space: 20 }) {
      // 页面标题
      Text("ArkTS泛型核心实战")
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .width('100%')
        .textAlign(TextAlign.Center);

      // 测试泛型运算函数
      Button("测试泛型运算函数")
        .width(220)
        .height(60)
        .onClick(() => {
          console.log("\n===== 泛型运算函数测试 =====");
          combineTest();
        });

      // 测试泛型筛选函数
      Button("测试泛型筛选函数")
        .width(220)
        .height(60)
        .onClick(() => {
          console.log("\n===== 泛型筛选函数测试 =====");
          filterArrTest();
        });

      // 测试泛型约束函数
      Button("测试泛型约束函数")
        .width(220)
        .height(60)
        .onClick(() => {
          console.log("\n===== 泛型约束函数测试 =====");
          filterObjArrTest();
        });

      // 测试泛型列表接口
      Button("测试泛型列表接口")
        .width(220)
        .height(60)
        .onClick(() => {
          console.log("\n===== 泛型列表接口测试 =====");
          listTest();
        });

      // 测试泛型计数器闭包
      Button("测试泛型计数器闭包")
        .width(220)
        .height(60)
        .onClick(() => {
          console.log("\n===== 泛型计数器闭包测试 =====");
          counterTest();
        });
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .padding(20);
  }
}

五、核心总结

  1. 泛型本质:通过类型参数<T>替代固定类型,实现“一份逻辑适配多类型”,从根源减少重复代码;
  2. 核心语法
    • 泛型函数:function 函数名<T>(param: T): T,支持基础类型和自定义接口类型适配;
    • 泛型约束:T extends 类型(如T extends object/T extends number | string),限定泛型范围,保障类型安全;
    • 泛型接口:interface IListData<T>,可适配任意业务类型的列表数据;
  3. 类型安全:通过类型守卫(如typeof a !== typeof b)和静态类型检查,提前拦截类型错误,避免运行时异常;
  4. 实战价值:基于IUser/IContact等自定义接口,泛型可快速适配不同业务场景,无需重复定义函数/接口。

六、代码仓库

七、下节预告

本节我们已掌握泛型基础核心能力,下一节将聚焦ArkTS内置泛型工具类型应用,核心内容包括:

  1. 系统学习Partial(字段可选化)、Required(字段必填化)、Pick(字段提取)、Record(键值对构造)、Readonly(只读约束)五大内置泛型工具类型的基础语法与鸿蒙实战场景;
  2. 对比TS标准Object.assign/展开运算符与ArkTS自定义合并函数的用法,理解ArkTS禁用原生合并语法的原因及兼容方案;
  3. 结合鸿蒙开发真实业务(如用户注册、数据更新、网络请求参数约束),落地工具类型的精准选型与使用,避免重复定义相似类型;
  4. 掌握ArkTS类型约束规范,通过工具类型在编译阶段规避字段相关错误,提升代码安全性与开发效率,帮助你快速掌握泛型工具类型在鸿蒙开发中的核心应用,为后续泛型进阶打下坚实基础。
posted @ 2026-01-21 16:20  鸿蒙-散修  阅读(0)  评论(0)    收藏  举报