鸿蒙 ArkTS 实战进阶:从核心组件到面向对象编程一篇通
前言
在上一篇《鸿蒙 ArkTS 实战全解:从基础布局到完整页面》中,我们掌握了 ArkTS 的标准页面结构、基础组件和响应式状态管理,已经能够写出简单可用的页面。这周我们继续深入,一方面攻克两个移动端开发中使用频率最高 的容器组件 ------Scroll 和 Tabs,解决绝大多数页面的滚动和导航需求;另一方面正式进入 ArkTS 的面向对象编程世界,学习 class、interface 和泛型,这是我们从 "能写代码" 到 "写好代码" 的关键转折点。
一、UI 组件实战篇:掌握滚动与导航的精髓
1.1 Scroll 组件:所有长页面的基础
Scroll 是 ArkUI 中最基础的可滚动容器,当子组件的尺寸超过父组件视口时自动提供滚动能力。它适用于静态内容展示、少量动态内容和需要自定义滚动行为的场景。
核心属性速查表
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| scrollDirection | ScrollDirection | Vertical | 滚动方向:垂直 / 水平 / 自由 |
| enableScrollInteraction | boolean | true | 是否允许用户手动滚动 |
| edgeEffect | EdgeEffect | Spring | 边缘效果:弹簧回弹 / 无 |
| scrollBar | BarState | Auto | 滚动条状态:自动 / 始终显示 / 隐藏 |
实战用法
typescript
// 1. 基础垂直滚动
Scroll() {
Column({ space: 12 }) {
ForEach(Array.from({ length: 20 }, (_, i) => i), (item) => {
Text(`列表项 ${item}`)
.width('100%')
.height(60)
.backgroundColor('#f5f5f5')
.borderRadius(8)
.textAlign(TextAlign.Center)
})
}
.padding(16)
}
.width('100%')
.height('100%')
// 2. 使用 Scroller 控制器实现程序化滚动
private scroller: Scroller = new Scroller();
// 滚动到顶部
this.scroller.scrollToEdge(Edge.Top, { animation: true });
// 滚动到指定位置
this.scroller.scrollTo({
yOffset: 500,
animation: { duration: 300, curve: Curve.EaseInOut }
});
常见业务场景实现
- 上拉加载更多 :监听
onReachEnd事件,在滚动到底部时触发数据加载 - 下拉刷新 :配合官方
Refresh组件使用,几行代码即可实现标准下拉刷新效果 - 吸顶效果 :监听
onDidScroll事件,根据滚动偏移量动态改变组件的位置和层级
💡 最佳实践 :超过 50 条数据的长列表不要用 Scroll,一定要用 List + LazyForEach 实现虚拟化滚动,否则会严重影响性能。
1.2 Tabs 组件:移动端导航的灵魂
Tabs 是实现标签页切换的核心组件,由 Tabs 容器、TabBar 标签栏和 TabContent 内容页三部分组成。它广泛应用于底部导航、顶部分类切换和页面分段展示等场景。
核心属性速查表
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| barPosition | BarPosition | Start | 标签栏位置:顶部 / 底部 |
| index | number | 0 | 默认选中的标签索引 |
| scrollable | boolean | true | 是否允许左右滑动切换 |
| barMode | BarMode | Fixed | 标签栏模式:固定宽度 / 可滚动 |
实战用法:底部导航栏(最常用场景)
typescript
@State currentIndex: number = 0;
private tabController: TabController = new TabController();
Tabs({ controller: this.tabController, barPosition: BarPosition.End }) {
TabContent() {
HomePage()
}
.tabBar(this.buildTabItem("首页", $r('app.media.icon_home'), 0))
.lazy(true) // 关键:非首页启用懒加载
.keepAlive(true) // 关键:加载后保活
TabContent() {
CategoryPage()
}
.tabBar(this.buildTabItem("分类", $r('app.media.icon_category'), 1))
.lazy(true)
.keepAlive(true)
TabContent() {
MinePage()
}
.tabBar(this.buildTabItem("我的", $r('app.media.icon_mine'), 2))
.lazy(true)
.keepAlive(true)
}
.width('100%')
.height('100%')
.onChange((index) => {
this.currentIndex = index;
})
@Builder
buildTabItem(title: string, icon: Resource, index: number) {
Column({ space: 4 }) {
Image(icon)
.width(24)
.height(24)
.fillColor(this.currentIndex === index ? Color.Blue : Color.Gray)
Text(title)
.fontSize(12)
.fontColor(this.currentIndex === index ? Color.Blue : Color.Gray)
}
.width('100%')
.height(56)
.justifyContent(FlexAlign.Center)
}
💡 最佳实践 :所有非首页的 TabContent 都必须设置 lazy: true,避免页面初始化时一次性渲染所有内容,大幅提升首屏加载速度。对于频繁切换的页面,同时设置 keepAlive: true,减少重复渲染开销。
二、面向对象编程篇:写出可维护的代码
2.1 Class 类:封装数据与行为
类是面向对象编程的基本单位,用于封装数据和操作数据的方法。在 ArkTS 中,我们大量使用类来封装数据模型、工具类和业务逻辑。
基本语法
typescript
// 定义一个用户类
class User {
// 成员属性
name: string;
age: number;
// 构造函数
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// 成员方法
sayHello(): void {
console.log(`你好,我是${this.name},今年${this.age}岁`);
}
}
// 使用类
const user = new User("张三", 20);
user.sayHello(); // 输出:你好,我是张三,今年20岁
继承
typescript
// 学生类继承自用户类
class Student extends User {
studentId: string;
constructor(name: string, age: number, studentId: string) {
super(name, age); // 调用父类构造函数
this.studentId = studentId;
}
// 重写父类方法
sayHello(): void {
super.sayHello(); // 调用父类方法
console.log(`我的学号是${this.studentId}`);
}
}
在 ArkTS 中的应用
最常见的用法是封装数据模型:
typescript
// 定义一个商品数据模型
class Product {
id: number;
name: string;
price: number;
imageUrl: string;
constructor(id: number, name: string, price: number, imageUrl: string) {
this.id = id;
this.name = name;
this.price = price;
this.imageUrl = imageUrl;
}
// 格式化价格
getFormattedPrice(): string {
return `¥${this.price.toFixed(2)}`;
}
}
// 在组件中使用
@State productList: Product[] = [
new Product(1, "华为手机", 3999, "https://example.com/phone.jpg"),
new Product(2, "华为平板", 2499, "https://example.com/tablet.jpg")
];
2.2 Interface 接口:定义契约
接口用于定义对象的结构和类型,它不包含具体实现,只描述对象应该具有哪些属性和方法。接口是 TypeScript/ArkTS 最强大的特性之一,能够帮助我们在编译时发现类型错误。
基本语法
typescript
// 定义一个用户接口
interface IUser {
name: string;
age: number;
sayHello(): void;
}
// 实现接口
class User implements IUser {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHello(): void {
console.log(`你好,我是${this.name}`);
}
}
可选属性和只读属性
typescript
interface IProduct {
readonly id: number; // 只读属性,创建后不能修改
name: string;
price: number;
description?: string; // 可选属性,可以不存在
}
在 ArkTS 中的应用
- 定义组件参数类型
typescript
interface ICardProps {
title: string;
content: string;
onClick?: () => void;
}
@Component
struct CustomCard {
@Prop props: ICardProps;
build() {
Column() {
Text(this.props.title)
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text(this.props.content)
.fontSize(14)
.margin({ top: 8 })
}
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
.onClick(() => {
this.props.onClick?.();
})
}
}
- 定义 API 返回值类型
typescript
interface IApiResponse<T> {
code: number;
message: string;
data: T;
}
interface IUserInfo {
id: number;
username: string;
avatar: string;
}
// 网络请求返回值类型
type UserInfoResponse = IApiResponse<IUserInfo>;
2.3 泛型接口与泛型类:编写通用代码
泛型允许我们在定义接口、类或函数时不指定具体类型,而是在使用时再指定。这大大提高了代码的复用性和灵活性。
泛型接口
typescript
// 定义一个通用的列表响应接口
interface IListResponse<T> {
list: T[];
total: number;
page: number;
pageSize: number;
}
// 使用时指定具体类型
type ProductListResponse = IListResponse<Product>;
type UserListResponse = IListResponse<User>;
泛型类
// 定义一个通用的栈类
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;
}
}
// 使用数字栈
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // 输出:2
// 使用字符串栈
const stringStack = new Stack<string>();
stringStack.push("a");
stringStack.push("b");
console.log(stringStack.pop()); // 输出:b
💡 最佳实践:当你发现自己在写多个结构相同但类型不同的类或接口时,就应该考虑使用泛型来重构代码,消除重复。
三、本周总结
这周我们完成了从 "基础组件使用" 到 "面向对象编程" 的跨越。掌握了 Scroll 和 Tabs 这两个核心组件,我们已经能够开发出绝大多数移动端常见的页面布局;而学习了类、接口和泛型,我们的代码开始变得更加结构化、可维护和可复用。
ArkTS 的学习曲线非常友好,它既保留了 JavaScript/TypeScript 的灵活性,又加入了强类型和面向对象的特性,非常适合移动端开发。只要我们一步一个脚印,把基础打扎实,很快就能开发出高质量的鸿蒙应用。