零基础鸿蒙应用开发第十七节:泛型基础入门与实战应用
【学习目标】
- 理解泛型的核心本质,明确其解决“类型固化、逻辑重复”问题的核心价值;
- 掌握泛型函数、泛型接口、泛型约束(
extends)的标准语法; - 能基于自定义业务接口(
IUser/IContact)落地泛型,实现“一套接口适配多类数据”; - 兼顾泛型的灵活性与类型安全,规避无约束泛型导致的编译/运行时错误。
【学习重点】
- 核心本质:用“类型参数
<T>”替代固定类型,让函数/接口/闭包适配多类型; - 核心语法:泛型函数
<T>、泛型接口interface IGeneric<T>、泛型约束T extends 类型; - 核心价值:从根源减少重复代码,兼顾类型复用与静态类型安全;
- 实战落地:泛型适配基础类型(
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 通用筛选函数(适配任意类型数组)
实现一份筛选逻辑,同时适配基础类型、IUser、IContact:
// 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>泛型接口,分别适配IUser和IContact列表:
// 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);
}
}
五、核心总结
- 泛型本质:通过类型参数
<T>替代固定类型,实现“一份逻辑适配多类型”,从根源减少重复代码; - 核心语法:
- 泛型函数:
function 函数名<T>(param: T): T,支持基础类型和自定义接口类型适配; - 泛型约束:
T extends 类型(如T extends object/T extends number | string),限定泛型范围,保障类型安全; - 泛型接口:
interface IListData<T>,可适配任意业务类型的列表数据;
- 泛型函数:
- 类型安全:通过类型守卫(如
typeof a !== typeof b)和静态类型检查,提前拦截类型错误,避免运行时异常; - 实战价值:基于
IUser/IContact等自定义接口,泛型可快速适配不同业务场景,无需重复定义函数/接口。
六、代码仓库
- 工程名称:
GenericBasicDemo - 代码仓库:https://gitee.com/juhetianxia321/harmony-os-code-base.git
七、下节预告
本节我们已掌握泛型基础核心能力,下一节将聚焦ArkTS内置泛型工具类型应用,核心内容包括:
- 系统学习
Partial(字段可选化)、Required(字段必填化)、Pick(字段提取)、Record(键值对构造)、Readonly(只读约束)五大内置泛型工具类型的基础语法与鸿蒙实战场景; - 对比TS标准
Object.assign/展开运算符与ArkTS自定义合并函数的用法,理解ArkTS禁用原生合并语法的原因及兼容方案; - 结合鸿蒙开发真实业务(如用户注册、数据更新、网络请求参数约束),落地工具类型的精准选型与使用,避免重复定义相似类型;
- 掌握ArkTS类型约束规范,通过工具类型在编译阶段规避字段相关错误,提升代码安全性与开发效率,帮助你快速掌握泛型工具类型在鸿蒙开发中的核心应用,为后续泛型进阶打下坚实基础。
浙公网安备 33010602011771号