li159 2025-11-26 23:02:46 发布前言
ArkUI 作为 HarmonyOS 应用开发的核心 UI 框架,凭借声明式语法、组件化架构和高效渲染能力,成为构建复杂界面的关键工具。但在实际开发中,开发者常面临多维度布局适配、交互逻辑复杂、组件复用性低等问题。本教程聚焦 ArkUI 组件的进阶用法,从布局技巧、交互增强到自定义组件开发,结合电商商品详情页实战案例,系统解决复杂场景下的开发难题,同时提供性能优化方案,帮助开发者打造高效、流畅的鸿蒙应用界面。
第一章 基础布局组件升级用法:突破布局瓶颈
1.1 Flex/Grid 嵌套布局:应对多维度界面结构
1.1.1 Flex 布局高级嵌套:实现复杂分区界面
Flex 布局是 ArkUI 中最常用的线性布局方式,通过嵌套可实现 “整体 - 局部” 分层结构。例如电商首页的 “顶部导航 + 分类栏 + 商品列表 + 底部 Tab” 布局:
typescript
运行
@Entry
@Component
struct HomePage {
build() {
// 外层垂直Flex:整体页面结构
Flex({ direction: FlexDirection.Column }) {
// 顶部导航栏(水平Flex)
Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
Text("商城首页").fontSize(20).fontWeight(FontWeight.Bold);
Button("我的").width(60).height(30);
}.width('100%').height(50).backgroundColor('#f5f5f5');
// 分类栏(水平Flex+滚动)
Scroll({ direction: ScrollDirection.Horizontal }) {
Flex({ justifyContent: FlexAlign.SpaceAround }) {
['推荐', '数码', '服装', '家居', '食品'].forEach((item) => {
Text(item).width(60).textAlign(TextAlign.Center);
});
}.width('100%').padding(10);
}.height(60);
// 商品列表(垂直Flex+网格嵌套)
Flex({ direction: FlexDirection.Column }) {
Grid() {
ForEach([1, 2, 3, 4, 5, 6], (index) => {
GridItem() {
// 商品卡片(垂直Flex)
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
Image($r('app.media.product')).width(100).height(100);
Text(`商品${index}`).marginTop(5);
Text(`¥${99 + index}`).fontColor('#ff4400').marginTop(5);
}.padding(10).backgroundColor('#fff').borderRadius(8);
});
});
}
.columnsTemplate('1fr 1fr 1fr') // 3列网格
.columnsGap(10)
.rowsGap(10)
.width('100%')
.padding(10);
}.flexGrow(1); // 占满剩余空间
// 底部Tab栏(水平Flex)
Flex({ justifyContent: FlexAlign.SpaceAround }) {
['首页', '分类', '购物车', '我的'].forEach((item) => {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
Image($r(`app.media.icon_${item}`)).width(24).height(24);
Text(item).fontSize(12).marginTop(2);
});
});
}.width('100%').height(60).backgroundColor('#f5f5f5');
}.width('100%').height('100%').backgroundColor('#f9f9f9');
}
}
关键技巧:- 外层 Flex 控制页面整体流向,内层通过 Flex/Grid 实现局部分区;
- 使用
flexGrow让中间内容区域自适应剩余空间; - 结合
Scroll组件实现横向 / 纵向滚动,避免内容溢出。
1.1.2 Grid 布局进阶:动态网格与自适应行列
Grid 布局适用于二维网格场景(如商品列表、相册),通过动态配置行列属性实现自适应:
typescript
运行
@Component
struct AdaptiveGridPage {
// 模拟不同屏幕宽度下的列数
@State columnCount: number = 3;
// 监听屏幕宽度变化
onPageShow() {
const windowWidth = getWindowWidth();
this.columnCount = windowWidth > 720 ? 4 : windowWidth > 480 ? 3 : 2;
}
build() {
Grid() {
ForEach([...Array(8).keys()], (index) => {
GridItem() {
Text(`Item ${index + 1}`)
.width('100%')
.height(80)
.textAlign(TextAlign.Center)
.backgroundColor('#fff')
.borderRadius(8);
};
});
}
.columnsTemplate(`repeat(${this.columnCount}, 1fr)`) // 动态列数
.columnsGap(10)
.rowsGap(10)
.width('100%')
.padding(10)
.onPageShow(() => this.onPageShow());
}
}
// 获取屏幕宽度工具函数
function getWindowWidth(): number {
const windowClass = getContext().windowManager.getCurrentWindow();
const windowSize = windowClass.getWindowProperties().windowRect;
return windowSize.width;
}
核心优势:- 通过
repeat()函数动态生成列模板,适配不同屏幕尺寸; - 结合生命周期或尺寸监听函数,实现布局实时响应。
1.2 自适应屏幕适配技巧:多设备统一体验
1.2.1 尺寸单位选择:vp/fp 的灵活运用
- vp(虚拟像素):根据屏幕密度自动缩放,适用于组件尺寸、间距;
- fp(字体像素):跟随系统字体大小设置,保证文字可读性。
- typescript
- 运行
// 错误示范:使用固定px导致不同屏幕显示不一致
Text("固定尺寸").width(200).height(50);
// 正确示范:使用vp/fp适配
Text("自适应尺寸").width(200).height(50) // 单位默认vp
Text("自适应字体").fontSize(16) // 单位默认fp
1.2.2 媒体查询:针对不同设备类型定制布局
通过@Media装饰器实现设备差异化布局:
typescript
运行
@Entry
@Component
struct MediaQueryPage {
@State isPad: boolean = false;
build() {
Column() {
if (this.isPad) {
// 平板布局:左右分栏
Flex() {
Text("侧边栏").width(200).height('100%').backgroundColor('#f5f5f5');
Text("主内容").flexGrow(1).height('100%').backgroundColor('#fff');
}.width('100%').height('100%');
} else {
// 手机布局:垂直堆叠
Column() {
Text("顶部栏").width('100%').height(50).backgroundColor('#f5f5f5');
Text("主内容").flexGrow(1).width('100%').backgroundColor('#fff');
}.width('100%').height('100%');
}
}
.onPageShow(() => {
this.isPad = getDeviceType() === 'tablet';
});
}
}
// 获取设备类型工具函数
function getDeviceType(): string {
return getContext().config.deviceType; // 返回phone/tablet/watch等
}
第二章 交互组件高级应用:增强用户体验
2.1 List 组件进阶:下拉刷新 + 上拉加载
List 是 ArkUI 中实现滚动列表的核心组件,结合Refresh和LazyForEach可实现分页加载:
typescript
运行
@Entry
@Component
struct ProductListPage {
@State data: number[] = [...Array(20).keys()]; // 初始数据
@State isRefreshing: boolean = false;
@State hasMore: boolean = true; // 是否有更多数据
// 下拉刷新逻辑
async onRefresh() {
this.isRefreshing = true;
// 模拟接口请求
await new Promise(resolve => setTimeout(resolve, 1500));
this.data = [...Array(20).keys()]; // 重置数据
this.hasMore = true;
this.isRefreshing = false;
}
// 上拉加载逻辑
async loadMore() {
if (!this.hasMore || this.isRefreshing) return;
// 模拟接口请求
await new Promise(resolve => setTimeout(resolve, 1000));
const newData = this.data.length + 10 > 50 ? [] : [...Array(10).keys()].map(i => this.data.length + i);
if (newData.length === 0) {
this.hasMore = false;
return;
}
this.data = [...this.data, ...newData];
}
build() {
Refresh({ refreshing: this.isRefreshing, onRefresh: this.onRefresh.bind(this) }) {
List() {
LazyForEach(new MyDataSource(this.data), (item) => {
ListItem() {
Text(`商品 ${item + 1}`)
.width('100%')
.height(80)
.lineHeight(80)
.textAlign(TextAlign.Center)
.backgroundColor('#fff')
.marginBottom(10);
}
});
// 加载更多提示
ListItem() {
Text(this.hasMore ? "加载中..." : "没有更多数据了")
.width('100%')
.height(50)
.textAlign(TextAlign.Center)
.fontSize(14)
.fontColor('#999');
}
}
.onReachEnd(() => this.loadMore()) // 上拉触底触发
.width('100%')
.padding(10)
.backgroundColor('#f9f9f9');
}
.width('100%')
.height('100%');
}
}
// 自定义数据源(优化性能)
class MyDataSource implements IDataSource {
private data: number[];
constructor(data: number[]) {
this.data = data;
}
totalCount(): number {
return this.data.length;
}
getData(index: number): number {
return this.data[index];
}
registerDataChangeListener(_: DataChangeListener): void {}
unregisterDataChangeListener(_: DataChangeListener): void {}
}
关键优化:- 使用
LazyForEach替代ForEach,实现列表项懒加载,减少内存占用; - 通过
onReachEnd监听列表触底,触发加载更多逻辑; - 加入状态控制(
isRefreshing/hasMore),避免重复请求。
2.2 弹窗组件自定义样式:从默认到个性化
ArkUI 提供AlertDialog/CustomDialog等弹窗组件,通过自定义实现差异化 UI:
2.2.1 自定义确认弹窗
typescript
运行
@Entry
@Component
struct CustomDialogPage {
@State showDialog: boolean = false;
build() {
Column() {
Button("打开自定义弹窗")
.onClick(() => this.showDialog = true)
.margin(20);
// 自定义弹窗
CustomDialog({
controller: this.dialogController,
builder: () => {
Column() {
Text("提示")
.fontSize(18)
.fontWeight(FontWeight.Bold)
.marginBottom(15);
Text("确定要删除这条数据吗?")
.fontSize(14)
.marginBottom(20);
Flex({ justifyContent: FlexAlign.End }) {
Button("取消")
.width(80)
.height(35)
.backgroundColor('#f5f5f5')
.fontColor('#333')
.onClick(() => this.dialogController.close());
Button("确定")
.width(80)
.height(35)
.backgroundColor('#ff4400')
.marginLeft(10)
.onClick(() => {
// 执行删除逻辑
this.dialogController.close();
});
}
}
.width(300)
.padding(20)
.backgroundColor('#fff')
.borderRadius(12);
},
cancel: () => console.log("弹窗取消"),
autoCancel: true // 点击空白处关闭
})
.visible(this.showDialog);
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center);
}
// 弹窗控制器
dialogController: CustomDialogController = new CustomDialogController({
builder: this.build.bind(this),
autoCancel: true,
alignment: DialogAlignment.Center,
offset: { dx: 0, dy: 0 },
customStyle: true // 启用自定义样式
});
}
2.2.2 底部弹出菜单(ActionSheet)
typescript
运行
@Component
struct ActionSheetPage {
@State showSheet: boolean = false;
build() {
Column() {
Button("打开底部菜单")
.onClick(() => this.showSheet = true)
.margin(20);
if (this.showSheet) {
Stack() {
// 遮罩层
Column().width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
.onClick(() => this.showSheet = false);
// 菜单内容
Column() {
Text("操作选项")
.width('100%')
.height(50)
.lineHeight(50)
.textAlign(TextAlign.Center)
.borderBottom({ width: 1, color: '#eee' });
['分享', '收藏', '举报'].forEach((item) => {
Text(item)
.width('100%')
.height(50)
.lineHeight(50)
.textAlign(TextAlign.Center)
.onClick(() => {
console.log(`选择了${item}`);
this.showSheet = false;
});
});
Text("取消")
.width('100%')
.height(50)
.lineHeight(50)
.textAlign(TextAlign.Center)
.backgroundColor('#f5f5f5')
.marginTop(10)
.onClick(() => this.showSheet = false);
}
.width('100%')
.backgroundColor('#fff')
.borderRadius({ topLeft: 12, topRight: 12 })
.position({ bottom: 0 });
}
.width('100%')
.height('100%');
}
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center);
}
}
第三章 自定义组件开发:封装复用与扩展
3.1 通用组件封装:以 “商品卡片” 为例
封装可复用的自定义组件,需考虑参数传递、事件回调和样式定制:
typescript
运行
// 商品卡片组件(ProductCard.ets)
@Component
export struct ProductCard {
// 组件参数(支持外部传入)
@Prop image: Resource = $r('app.media.default_product');
@Prop title: string = "默认商品";
@Prop price: number = 0;
@Prop tag?: string; // 可选标签(如“新品”“热销”)
// 事件回调(点击事件)
onClick: () => void = () => {};
build() {
Column() {
// 商品图片(带标签)
Stack() {
Image(this.image)
.width('100%')
.height(180)
.objectFit(ImageFit.Cover)
.borderRadius({ topLeft: 8, topRight: 8 });
if (this.tag) {
Text(this.tag)
.fontSize(12)
.backgroundColor('#ff4400')
.fontColor('#fff')
.padding({ left: 5, right: 5, top: 2, bottom: 2 })
.borderRadius(4)
.position({ top: 10, left: 10 });
}
}
// 商品信息
Column() {
Text(this.title)
.fontSize(14)
.fontWeight(FontWeight.Medium)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis });
Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
Text(`¥${this.price.toFixed(2)}`)
.fontSize(16)
.fontColor('#ff4400')
.fontWeight(FontWeight.Bold);
Button("加入购物车")
.width(80)
.height(28)
.fontSize(12)
.backgroundColor('#ff4400')
.fontColor('#fff');
}.marginTop(8);
}
.padding(10)
.width('100%')
.backgroundColor('#fff')
.borderRadius({ bottomLeft: 8, bottomRight: 8 });
}
.width('100%')
.backgroundColor('#fff')
.borderRadius(8)
.shadow({ radius: 4, color: 'rgba(0,0,0,0.1)', offsetX: 0, offsetY: 2 })
.onClick(this.onClick);
}
}
使用自定义组件:
typescript
运行
@Entry
@Component
struct ProductList {
build() {
Grid() {
ForEach([
{ image: $r('app.media.phone'), title: "鸿蒙智能手机", price: 2999, tag: "热销" },
{ image: $r('app.media.tablet'), title: "鸿蒙平板", price: 1999, tag: "新品" },
{ image: $r('app.media.watch'), title: "鸿蒙智能手表", price: 1299 }
], (item) => {
GridItem() {
ProductCard({
image: item.image,
title: item.title,
price: item.price,
tag: item.tag,
onClick: () => console.log(`点击了${item.title}`)
});
};
});
}
.columnsTemplate('1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.width('100%')
.padding(10);
}
}
3.2 组件通信:父子 / 兄弟组件数据传递
3.2.1 父子组件通信:@Prop/@Link/@Provide/@Consume
- @Prop:父组件向子组件单向传递数据(子组件无法修改父组件数据);
- @Link:父子组件双向数据绑定(子组件修改会同步到父组件);
- @Provide/@Consume:跨层级组件通信(避免多层级 props 传递)。
- 示例:@Link 双向绑定
- typescript
- 运行
// 子组件
@Component
struct CounterChild {
@Link count: number;
build() {
Button(`子组件:${this.count}`)
.onClick(() => this.count++);
}
}
// 父组件
@Entry
@Component
struct CounterParent {
@State count: number = 0;
build() {
Column() {
Text(`父组件:${this.count}`).fontSize(20).margin(20);
CounterChild({ count: $count }); // $表示双向绑定
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center);
}
}
3.2.2 兄弟组件通信:全局事件总线
通过EventHub实现非父子组件通信:
typescript
运行
// 全局事件总线工具类
class EventBus {
private static instance: EventBus;
private eventHub: EventHub;
private constructor() {
this.eventHub = getContext().eventHub;
}
static getInstance(): EventBus {
if (!EventBus.instance) {
EventBus.instance = new EventBus();
}
return EventBus.instance;
}
// 发送事件
emit(eventName: string, data?: any) {
this.eventHub.emit(eventName, data);
}
// 订阅事件
on(eventName: string, callback: (...args: any[]) => void) {
this.eventHub.on(eventName, callback);
}
// 取消订阅
off(eventName: string, callback?: (...args: any[]) => void) {
this.eventHub.off(eventName, callback);
}
}
// 组件A(发送事件)
@Component
struct ComponentA {
build() {
Button("发送消息")
.onClick(() => {
EventBus.getInstance().emit("message", { text: "Hello from ComponentA" });
});
}
}
// 组件B(接收事件)
@Component
struct ComponentB {
@State message: string = "";
aboutToAppear() {
EventBus.getInstance().on("message", (data) => {
this.message = data.text;
});
}
aboutToDisappear() {
EventBus.getInstance().off("message"); // 取消订阅,避免内存泄漏
}
build() {
Text(`接收的消息:${this.message}`).fontSize(16);
}
}
第四章 实战案例:电商商品详情页实现
4.1 页面整体结构:滑动布局与分区设计
商品详情页通常包含 “轮播图、商品信息、规格选择、详情内容” 等模块,通过Scroll+Stack实现复杂布局:
typescript
运行
@Entry
@Component
struct ProductDetailPage {
@State currentTab: number = 0; // 当前选中的标签页
@State selectedSpec: string = "默认规格"; // 选中的商品规格
@State showSpecModal: boolean = false; // 是否显示规格选择弹窗
build() {
Stack() {
// 主滚动内容
Scroll() {
Column() {
// 1. 商品轮播图
Swiper() {
ForEach([1, 2, 3], (index) => {
Image($r(`app.media.product_${index}`))
.width('100%')
.height(300)
.objectFit(ImageFit.Cover);
});
}
.height(300)
.autoPlay(true)
.indicator(true);
// 2. 商品基本信息
Column() {
Text("鸿蒙智能手表 运动版")
.fontSize(20)
.fontWeight(FontWeight.Bold)
.marginBottom(5);
Text("¥1299")
.fontSize(24)
.fontColor('#ff4400')
.fontWeight(FontWeight.Bold)
.marginBottom(10);
Text("【新品上市】支持鸿蒙4.0,超长续航,心率监测")
.fontSize(14)
.fontColor('#666')
.marginBottom(15);
// 规格选择栏
Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
Text("选择规格:")
.fontSize(14);
Text(this.selectedSpec)
.fontSize(14)
.fontColor('#ff4400')
.onClick(() => this.showSpecModal = true);
}
.width('100%')
.padding(10)
.backgroundColor('#f9f9f9')
.borderRadius(8)
.marginBottom(15);
}
.width('100%')
.padding(15)
.backgroundColor('#fff');
// 3. 标签页切换(商品详情/规格参数/用户评价)
Column() {
// 标签栏
Flex() {
['商品详情', '规格参数', '用户评价'].forEach((item, index) => {
Text(item)
.fontSize(16)
.fontWeight(this.currentTab === index ? FontWeight.Bold : FontWeight.Normal)
.fontColor(this.currentTab === index ? '#ff4400' : '#333')
.width('100%')
.textAlign(TextAlign.Center)
.padding(10)
.onClick(() => this.currentTab = index);
if (index < 2) {
Divider().vertical().height(20).color('#eee');
}
});
}
.width('100%')
.backgroundColor('#fff');
// 标签内容
Stack() {
if (this.currentTab === 0) {
// 商品详情
Column() {
Image($r('app.media.detail_1')).width('100%');
Image($r('app.media.detail_2')).width('100%');
Image($r('app.media.detail_3')).width('100%');
}
} else if (this.currentTab === 1) {
// 规格参数
Column() {
['品牌:鸿蒙', '型号:Watch Pro', '续航:7天', '防水:50米'].forEach((item) => {
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
Text(item.split(':')[0]).fontColor('#666');
Text(item.split(':')[1]).fontColor('#333');
}
.width('100%')
.padding(10)
.borderBottom({ width: 1, color: '#eee' });
});
}.padding(10);
} else {
// 用户评价
Column() {
ForEach([
{ name: "用户A", content: "续航超给力,推荐购买!", rating: 5 },
{ name: "用户B", content: "操作流畅,鸿蒙生态体验好", rating: 4 }
], (item) => {
Column() {
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
Text(item.name).fontWeight(FontWeight.Bold);
Rating({ rating: item.rating, indicator: true }).width(80);
}.marginBottom(5);
Text(item.content).fontSize(14).fontColor('#666');
}
.width('100%')
.padding(10)
.borderBottom({ width: 1, color: '#eee' });
});
}.padding(10);
}
}
.width('100%')
.backgroundColor('#fff')
.padding(10);
}
.width('100%')
.marginTop(10);
}
.width('100%');
}
.width('100%')
.height('100%');
// 底部操作栏(悬浮固定)
Stack() {
Flex({ justifyContent: FlexAlign.SpaceAround, alignItems: ItemAlign.Center }) {
Button("加入购物车")
.width('40%')
.height(45)
.backgroundColor('#fff')
.fontColor('#ff4400')
.border({ width: 1, color: '#ff4400' });
Button("立即购买")
.width('40%')
.height(45)
.backgroundColor('#ff4400')
.fontColor('#fff');
}
.width('100%')
.height(60)
.backgroundColor('#fff')
.position({ bottom: 0 });
}
// 规格选择弹窗
if (this.showSpecModal) {
Stack() {
// 遮罩层
Column().width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
.onClick(() => this.showSpecModal = false);
// 规格内容
Column() {
Text("选择商品规格")
.fontSize(18)
.fontWeight(FontWeight.Bold)
.marginBottom(20);
// 规格选项
Wrap({ spacing: 10 }) {
['黑色-46mm', '白色-46mm', '黑色-42mm', '白色-42mm'].forEach((spec) => {
Text(spec)
.padding({ left: 15, right: 15, top: 8, bottom: 8 })
.backgroundColor(this.selectedSpec === spec ? '#ff4400' : '#f5f5f5')
.fontColor(this.selectedSpec === spec ? '#fff' : '#333')
.borderRadius(20)
.onClick(() => this.selectedSpec = spec);
});
}
.marginBottom(30);
Button("确定")
.width('100%')
.height(45)
.backgroundColor('#ff4400')
.fontColor('#fff')
.onClick(() => this.showSpecModal = false);
}
.width('80%')
.padding(20)
.backgroundColor('#fff')
.borderRadius(12)
.position({ top: '30%' });
}
.width('100%')
.height('100%');
}
}
.width('100%')
.height('100%')
.backgroundColor('#f9f9f9');
}
}
4.2 交互增强:滑动动效与手势处理
4.2.1 轮播图缩放动效
通过Swiper的onChange事件结合动画,实现轮播图切换时的缩放效果:
typescript
运行
Swiper({ onChange: (index) => this.currentIndex = index }) {
ForEach([1, 2, 3], (item, index) => {
Image($r(`app.media.product_${item}`))
.width('100%')
.height(300)
.objectFit(ImageFit.Cover)
.scale({ x: this.currentIndex === index ? 1 : 0.95, y: this.currentIndex === index ? 1 : 0.95 })
.animation({ duration: 300, curve: Curve.EaseInOut });
});
}
4.2.2 下拉放大图片
监听滚动事件,实现下拉时轮播图放大效果:
typescript
运行
@State scrollOffset: number = 0; // 滚动偏移量
build() {
Scroll({ onScroll: (offset) => this.scrollOffset = offset.y }) {
// 轮播图(根据滚动偏移量放大)
Image($r('app.media.product_1'))
.width('100%')
.height(300 + Math.max(0, -this.scrollOffset) / 2) // 下拉时高度增加
.objectFit(ImageFit.Cover);
// ...其他内容
}
}
第五章 组件性能优化:避免卡顿与过度渲染
5.1 减少渲染开销:状态管理与条件渲染
5.1.1 合理使用 @State/@Link:避免不必要的更新
- 仅将需要响应式更新的数据声明为
@State/@Link; - 复杂对象拆分多个状态变量,避免整体更新导致的全量渲染。
- 反面示例:
- typescript
- 运行
@State product: { name: string, price: number, stock: number } = {
name: "鸿蒙手机",
price: 2999,
stock: 100
};
// 修改stock时,整个product对象更新,导致依赖product的组件全部重渲染
updateStock() {
this.product = { ...this.product, stock: this.product.stock - 1 };
}
优化示例:
typescript
运行
@State productName: string = "鸿蒙手机";
@State productPrice: number = 2999;
@State productStock: number = 100;
// 仅更新stock状态,依赖stock的组件才会重渲染
updateStock() {
this.productStock -= 1;
}
5.1.2 条件渲染优化:使用if替代visibility
visibility: Hidden会保留组件布局空间且仍会渲染,而if条件不满足时组件不会渲染:
typescript
运行
// 低效:组件仍会渲染,仅隐藏
Text("暂无数据").visibility(this.data.length === 0 ? Visibility.Visible : Visibility.Hidden);
// 高效:条件不满足时组件不渲染
if (this.data.length === 0) {
Text("暂无数据");
}
5.2 列表性能优化:懒加载与数据复用
5.2.1 强制使用 LazyForEach
对于长列表(超过 20 项),必须使用LazyForEach替代ForEach,实现列表项按需创建和回收:
typescript
运行
List() {
LazyForEach(new LargeDataSource(), (item) => {
ListItem() {
Text(`Item ${item}`).width('100%').height(80).textAlign(TextAlign.Center);
}
});
}
5.2.2 列表项高度固定
为 List 设置estimatedItemSize或固定列表项高度,减少布局计算开销:
typescript
运行
List() {
// 列表项
}
.estimatedItemSize(80) // 预估列表项高度
5.3 图片优化:懒加载与格式选择
5.3.1 图片懒加载
结合IntersectionObserver实现图片懒加载(进入可视区域后加载):
typescript
运行
@Component
struct LazyImage {
@Prop src: string;
@State loaded: boolean = false;
aboutToAppear() {
// 创建交叉观察者
const observer = getContext().intersectionObserver.createObserver({
thresholds: [0.1] // 元素10%进入可视区域时触发
});
observer.observe(this.id, (result) => {
if (result.intersectionRatio > 0) {
this.loaded = true;
observer.disconnect(); // 取消观察
}
});
}
build() {
Image(this.loaded ? this.src : $r('app.media.placeholder')) // 占位图
.width('100%')
.height(200)
.id(`lazy-image-${this.src}`);
}
}
5.3.2 选择高效图片格式
优先使用 WebP 格式(体积比 PNG 小 30%-50%),并根据设备分辨率加载对应尺寸图片:
typescript
运行
Image($r('app.media.product.webp')) // WebP格式图片
.width('100%')
.height(200);
结语
ArkUI 组件的进阶应用核心在于布局分层设计、交互逻辑解耦和性能优化意识。通过 Flex/Grid 嵌套实现复杂界面结构,结合自定义组件提升复用性,再通过懒加载、状态管理优化性能,可高效构建流畅的鸿蒙应用界面。
相关推荐
1361
0
1656
0
没空恋爱的工程师
3658
0
3647
0
li159
我还没有写个人简介......
帖子
提问
粉丝
HarmonyOS 应用安全开发指南:从代码到上线的全链路防护
2025-11-30 22:18:22 发布HarmonyOS 端侧AI能力集成实战:打造智能语音助手
2025-11-30 22:14:36 发布