
引言
性能优化是应用从"能用"到"好用"的关键一步。在节气通这类内容型应用中,长列表滚动流畅度、图片加载速度、页面切换动画的顺滑程度、内存占用等直接影响用户体验。HarmonyOS ArkUI 提供了 LazyForEach 懒加载、CachedImage 缓存图片、@Reusable 组件复用等原生能力,配合合理的架构设计,可以打造出高性能的应用。
一个经过优化的应用应该具备以下特点:
- 列表流畅:长列表(1000+项)滚动不掉帧(FPS≥55)
- 启动快速:冷启动时间 < 2秒
- 内存合理:长时间运行不出现内存持续增长
- 响应及时:用户操作反馈 < 100ms
- 省电省流:后台不消耗不必要的资源
本文为实战总结版本,涵盖 LazyForEach 列表优化、图片加载与缓存策略、内存管理、启动优化、以及性能监控工具使用。
学习目标
完成本文后,你将能够:
- ✅ 使用 LazyForEach 实现高效的长列表渲染
- ✅ 优化图片加载策略(缓存/压缩/懒加载)
- ✅ 管理应用内存,避免泄漏
- ✅ 优化应用启动速度
- ✅ 使用 Profiler 工具定位性能瓶颈
- ✅ 建立性能基线和回归防护
需求分析
性能优化维度
| 维度 | 优化目标 | 关键指标 | 核心手段 |
|---|---|---|---|
| 列表渲染 | 大数据量下保持流畅 | FPS ≥ 55 | LazyForEach + @Reusable |
| 图片加载 | 快速显示且不卡顿 | 首图 < 500ms | 多级缓存 + 占位图 |
| 内存管理 | 无泄漏、无膨胀 | 稳定 < 150MB | 及时释放、避免循环引用 |
| 启动速度 | 快速进入可用状态 | 冷启 < 2s | 异步加载、延迟初始化 |
| 包体积 | 下载安装快速 | < 15MB | 资源压缩、按需加载 |
| 电量消耗 | 后台低功耗 | 待机 < 1%/h | 减少唤醒、批量任务 |
项目中的性能关键场景
| 场景 | 当前挑战 | 优化方向 |
|---|---|---|
| 节气列表页 | 24个节气卡片 + 详情展开 | LazyForEach + 条件渲染 |
| 文章详情页 | 富文本 + 多张图片 | 图片懒加载 + 分段渲染 |
| 百科浏览页 | 分类筛选 + 搜索过滤 | 数据预处理 + 虚拟化 |
| 设置页 | 主题切换全局刷新 | @StorageProp + 局部更新 |
| 启动流程 | 初始化服务 + 加载数据 | 并行化 + 延迟加载 |
核心实现
步骤1: LazyForEach ------ 高效列表渲染
LazyForEach 是 ArkUI 提供的懒加载列表组件,只渲染可视区域内的 item,大幅降低长列表的内存和渲染开销。
基础用法对比
typescript
// ❌ ForEach:一次性创建所有子组件(数据量大时卡顿)
@State items: ItemData[] = []; // 假设有1000条数据
List() {
ForEach(this.items, (item: ItemData) => {
ListItem() {
// 每个 item 都会被创建并加入渲染树
Text(item.title)
}
})
}
// ✓ LazyForEach:按需创建,仅渲染可见区域
@State items: ItemData[] = [];
private dataSource: BasicDataSource = new BasicDataSource();
aboutToAppear(): void {
this.dataSource.reloadData(this.items);
}
List() {
LazyForEach(this.dataSource, (item: ItemData, index: number) => {
ListItem() {
Text(item.title)
}
}, (item: ItemData, index: number) => {
// key生成器:确保每个item有唯一标识
return `${item.id}_${index}`;
})
}
DataSource 实现
typescript
// models/ListDataSource.ts
/**
* 基础数据源 - LazyForEach 必须继承自 IDataSource
*
* IDataSource 接口要求:
* totalCount(): 返回总数据量
* getData(index): 返回指定索引的数据
* registerDataChangeListener / unregisterDataChangeListener: 监听器管理
*/
export class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = [];
private data: any[] = [];
/** 获取总数 */
totalCount(): number {
return this.data.length;
}
/** 获取指定位置的数据 */
getData(index: number): any {
return this.data[index];
}
/** 注册监听器 */
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
this.listeners.push(listener);
}
}
/** 注销监听器 */
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
this.listeners.splice(pos, 1);
}
}
/** 通知数据已重载 */
notifyDataReload(): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataReloaded();
});
}
/** 通知某条数据变化 */
notifyDataAdd(index: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataAdd(index);
});
}
/** 通知某条数据被删除 */
notifyDataDelete(index: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataDelete(index);
});
}
/** 通知数据变化(通用) */
notifyDataChange(index: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataChange(index);
});
}
/** 通知范围变化 */
notifyDataChangeRange(from: number, to: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataChangeRange(from, to);
});
}
/** 重新加载数据 */
reloadData(data: any[]): void {
this.data = data;
this.notifyDataReload();
}
/** 在末尾添加数据 */
addData(data: any): void {
this.data.push(data);
this.notifyDataAdd(this.data.length - 1);
}
/** 在指定位置插入 */
insertData(index: number, data: any): void {
this.data.splice(index, 0, data);
this.notifyDataAdd(index);
}
/** 删除指定位置数据 */
deleteData(index: number): void {
this.data.splice(index, 1);
this.notifyDataDelete(index);
}
}
节气列表完整示例
typescript
// pages/SolarTermList.ets
import { BasicDataSource } from '../models/ListDataSource';
import { SolarTermData } from '../models/SolarTermModel';
@Component
struct SolarTermListItem {
@Prop term: SolarTermData;
@Prop isExpanded: boolean = false;
build() {
Column() {
// 卡片头部(始终渲染)
Row() {
Column({ space: 4 }) {
Text(this.term.name)
.fontSize(17)
.fontWeight(FontWeight.Bold)
Text(`${this.term.solarDate} · ${this.term.lunarDate}`)
.fontSize(12)
.fontColor('#888888')
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
// 展开指示箭头
Image($r('app.media.icon_arrow_down'))
.width(16)
.height(16)
.rotate({ angle: this.isExpanded ? 90 : 0 })
.animation({ duration: 200 })
}
.width('100%')
.padding(16)
// 展开详情(条件渲染:未展开时不创建DOM节点)
if (this.isExpanded) {
Divider().margin({ left: 16, right: 16 })
Text(this.term.description)
.fontSize(14)
.fontColor('#555555')
.textAlign(TextAlign.Justify)
.lineHeight(20)
.margin({ left: 16, right: 16, bottom: 16 })
// 习俗标签
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.term.customs, (custom: string) => {
Text(custom)
.fontSize(11)
.fontColor('#4A9B6D')
.backgroundColor('#EDF5EE')
.borderRadius(12)
.padding({ left: 10, right: 10, top: 4, bottom: 4 })
.margin({ right: 8, top: 4 })
})
}
.margin({ left: 16, right: 16, bottom: 16 })
}
}
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({ radius: 4, color: '#00000008', offsetX: 0, offsetY: 2 })
}
}
@Entry
@Component
struct SolarTermListPage {
private dataSource: BasicDataSource = new BasicDataSource();
@State expandedIndex: number = -1; // 当前展开的索引
aboutToAppear(): void {
// 加载24个节气数据
const terms = this.loadSolarTerms();
this.dataSource.reloadData(terms);
}
build() {
Column() {
// 顶部标题栏
Row() {
Text('二十四节气')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.padding(16)
.justifyContent(FlexAlign.Center)
// 关键:LazyForEach 替代 ForEach
List({ space: 12 }) {
LazyForEach(this.dataSource, (term: SolarTermData, index: number) => {
ListItem() {
SolarTermListItem({
term: term,
isExpanded: this.expandedIndex === index
})
.onClick(() => {
// 点击切换展开/收起
animateTo({ duration: 200, curve: 'easeInOut' }, () => {
this.expandedIndex =
this.expandedIndex === index ? -1 : index;
});
})
}
}, (term: SolarTermData, index: number) => {
return term.name; // 用名称作为唯一key
})
}
.width('100%')
.layoutWeight(1)
.listDirection(Axis.Vertical)
.edgeEffect(EdgeEffect.Spring)
.scrollBar(BarState.Off)
.padding({ left: 16, right: 16 })
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
private loadSolarTerms(): SolarTermData[] {
// 从数据服务或本地资源加载
return [
{ name: '立春', pinyin: 'Li Chun', solarDate: '2026-02-04',
lunarDate: '正月十七', description: '立春,二十四节气之首。',
customs: ['迎春', '咬春'] },
{ name: '雨水', pinyin: 'Yu Shui', solarDate: '2026-02-19',
lunarDate: '正月初二', description: '雨水节气表示降雨开始。',
customs: ['接寿', '拉保保'] },
// ... 其余22个节气
];
}
}
步骤2: 图片优化策略
图片是移动应用中最大的资源消耗者之一。合理的图片策略可以显著减少内存占用和网络流量。
多级缓存策略
typescript
// utils/ImageCacheManager.ets
import image from '@ohos.image';
import fs from '@ohos.file.fs';
/** 图片缓存级别 */
enum CacheLevel {
MEMORY = 'memory', // 内存缓存(最快)
DISK = 'disk' // 磁盘缓存(次快)
}
/** 图片缓存管理器 */
class ImageCacheManager {
private static instance: ImageCacheManager;
private memoryCache: Map<string, image.PixelMap> = new Map();
private readonly MAX_MEMORY_CACHE = 50; // 最多缓存50张图片在内存中
private diskCacheDir: string = ''; // 磁盘缓存目录
static getInstance(): ImageCacheManager {
if (!ImageCacheManager.instance) {
ImageCacheManager.instance = new ImageCacheManager();
}
return ImageCacheManager.instance;
}
/**
* 获取图片(三级查找)
* 1. 内存缓存 → 2. 磁盘缓存 → 3. 网络加载
*/
async getImage(url: string): Promise<image.PixelMap | null> {
// Level 1: 内存缓存
const memCached = this.memoryCache.get(url);
if (memCached) {
console.info(`[ImageCache] 命中内存缓存: ${url}`);
return memCached;
}
// Level 2: 磁盘缓存
const diskCached = await this.loadFromDisk(url);
if (diskCached) {
console.info(`[ImageCache] 命中磁盘缓存: ${url}`);
this.putMemoryCache(url, diskCached);
return diskCached;
}
// Level 3: 网络加载
console.info(`[ImageCache] 网络加载: ${url}`);
const loaded = await this.loadFromNetwork(url);
if (loaded) {
this.putMemoryCache(url, loaded);
this.saveToDisk(url, loaded);
}
return loaded;
}
/** 放入内存缓存(LRU淘汰) */
private putMemoryCache(key: string, pixelMap: image.PixelMap): void {
// 超过容量时移除最早缓存的
if (this.memoryCache.size >= this.MAX_MEMORY_CACHE) {
const firstKey = this.memoryCache.keys().next().value;
this.memoryCache.delete(firstKey);
}
this.memoryCache.set(key, pixelMap);
}
private async loadFromDisk(_url: string): Promise<image.PixelMap | null> {
// TODO: 从文件系统读取缓存图片
return null;
}
private async saveToDisk(_url: string, _pixelMap: image.PixelMap): Promise<void> {
// TODO: 将图片保存到磁盘缓存目录
}
private async loadFromNetwork(_url: string): Promise<image.PixelMap | null> {
// TODO: 通过HTTP请求下载图片并解码为PixelMap
return null;
}
/** 清除所有缓存 */
clearAll(): void {
this.memoryCache.clear();
// 同时清除磁盘缓存...
}
}
图片组件封装(带占位、错误处理)
typescript
// components/OptimizedImage.ets
@Component
export struct OptimizedImage {
@Prop imageUrl: string = '';
@Prop placeholderRes: Resource = $r('app.media.img_placeholder');
@Prop errorRes: Resource = $r('app.media.img_error');
@Prop borderRadius: number = 8;
@State loadStatus: 'loading' | 'success' | 'error' = 'loading';
build() {
Stack({ alignContent: Alignment.Center }) {
// 实际图片
if (this.imageUrl && this.loadStatus !== 'error') {
Image(this.imageUrl)
.objectFit(ImageFit.Cover)
.width('100%')
.height('100%')
.borderRadius(this.borderRadius)
.onComplete(() => {
this.loadStatus = 'success';
})
.onError(() => {
this.loadStatus = 'error';
})
.visibility(this.loadStatus === 'success' ? Visibility.Visible : Visibility.Hidden)
}
// 占位图(加载中)
if (this.loadStatus === 'loading') {
Image(this.placeholderRes)
.width('60%')
.height('60%')
.objectFit(ImageFit.Contain)
.opacity(0.6)
}
// 错误图(加载失败)
if (this.loadStatus === 'error') {
Image(this.errorRes)
.width('40%')
.height('40%')
.objectFit(ImageFit.Contain)
.opacity(0.5)
}
}
.width('100%')
.height(180)
.backgroundColor('#F8F8F8')
}
}
图片优化要点总结
| 优化点 | 具体措施 | 效果 |
|---|---|---|
| 尺寸适配 | 服务端返回适合屏幕尺寸的图片 | 减少70%+传输量 |
| 格式选择 | WebP > JPEG > PNG | WebP比JPEG小25-35% |
| 懒加载 | 进入视口才加载 | 首屏速度提升明显 |
| 内存缓存 | 最近查看的图片常驻内存 | 二次打开瞬间显示 |
| 磁盘缓存 | 已下载图片持久化存储 | 离线可用 |
| 渐进式加载 | 先模糊后清晰 | 用户感知更快 |
| 占位图 | 加载前显示统一占位 | 防止布局抖动 |
步骤3: 内存管理
常见内存问题与解决方案
问题1: 循环引用导致无法释放
typescript
// ❌ 错误:闭包持有 this 引用
@Component
struct LeakExample {
@State timerId: number = -1;
aboutToAppear(): void {
// setInterval 的回调捕获了 this
this.timerId = setInterval(() => {
console.info(this.toString()); // this 引用导致组件无法被GC
}, 1000);
}
aboutToDisappear(): void {
clearInterval(this.timerId); // 即使清除定时器,闭包仍可能阻止回收
}
}
// ✓ 正确:使用局部变量避免闭包引用
@Component
struct FixedExample {
private timerId: number = -1;
aboutToAppear(): void {
let count = 0; // 局部变量,不影响组件生命周期
this.timerId = setInterval(() => {
count++;
console.info(`tick: ${count}`);
}, 1000);
}
aboutToDisappear(): void {
if (this.timerId !== -1) {
clearInterval(this.timerId);
this.timerId = -1;
}
}
}
问题2: 大对象未及时释放
typescript
// ❌ 问题:大数组长期持有
@State allArticles: Article[] = []; // 可能包含数百篇文章全文
// ✓ 方案:分页加载 + 按需获取详情
@State articleIds: string[] = []; // 只存ID列表(轻量)
@State currentArticle: Article | null = null; // 只存当前查看的一篇
async loadArticleDetail(id: string): Promise<void> {
this.currentArticle = await apiService.getArticle(id);
}
// 页面销毁时释放
aboutToDisappear(): void {
this.currentArticle = null;
}
问题3: PixelMap 未释放
typescript
// ❌ 问题:PixelMap 是 native 对象,需要手动释放
let pixelMap: image.PixelMap | null = null;
pixelMap = await image.createPixelMap(source);
// ✓ 正确:使用完毕后释放
try {
pixelMap = await image.createPixelMap(source);
// 使用 pixelMap ...
} finally {
if (pixelMap) {
pixelMap.release(); // 释放native内存!
pixelMap = null;
}
}
内存监控工具类
typescript
// utils/MemoryMonitor.ets
import process from '@ohos.process';
import hilog from '@ohos.hilog';
class MemoryMonitor {
private static instance: MemoryMonitor;
private lastLogTime: number = 0;
private readonly LOG_INTERVAL_MS = 5000; // 至少间隔5秒打印一次
static getInstance(): MemoryMonitor {
if (!MemoryMonitor.instance) {
MemoryMonitor.instance = new MemoryMonitor();
}
return MemoryMonitor.instance;
}
/** 记录当前内存使用情况 */
logMemoryUsage(tag: string = ''): void {
const now = Date.now();
if (now - this.lastLogTime < this.LOG_INTERVAL_MS) return;
this.lastLogTime = now;
const info = process.getInfo();
const mb = (bytes: number) => (bytes / 1024 / 1024).toFixed(1);
hilog.info(
0x0000, 'MemoryMonitor',
`[${tag}] RSS=${mb(info.rss)}MB | ` +
`VSize=${mb(info.vsize)}MB`
);
}
/** 检查是否接近内存警戒线 */
isMemoryHigh(): boolean {
const info = process.getInfo();
const rssMB = info.rss / 1024 / 1024;
// 手机应用通常超过200MB就需要关注
return rssMB > 200;
}
}
步骤4: 启动速度优化
冷启动流程与优化点
用户点击图标
│
▼
┌─────────────────────────────────┐
│ 1. 系统加载进程 & Ability │ ← 无法优化(系统行为)
└──────────────┬──────────────────┘
│
▼
┌─────────────────────────────────┐
│ 2. aboutToAppear 执行 │ ← 优化重点区域
│ ├─ 初始化 Service 单例 │ → 延迟到首次使用时初始化
│ ├─ 读取 Preferences 配置 │ → 并行读取多个key
│ ├─ 加载首屏数据 │ → 只加载必要字段
│ └─ 创建 UI 组件树 │ → 减少首屏组件数量
└──────────────┬──────────────────┘
│
▼
┌─────────────────────────────────┐
│ 3. 首帧渲染完成 │ ← 用户看到的第一帧
└─────────────────────────────────┘
优化策略代码示例
typescript
// pages/IndexPage.ets --- 启动优化版
@Entry
@Component
struct IndexPage {
@State welcomeText: string = '';
@State isLoading: boolean = true;
@State bannerData: BannerItem[] = [];
aboutToAppear(): void {
// ====== 优化1: 并行执行独立任务 ======
const tasks = [
this.loadWelcomeText(), // 任务1: 加载欢迎语
this.loadBannerData(), // 任务2: 加载轮播数据
this.initServicesLazy() // 任务3: 延迟初始化非必要服务
];
// 不需要 await 全部完成,各自独立更新UI
// Promise.allSettled(tasks);
// ====== 优化2: 先显示骨架屏,数据到了再替换 ======
// isLoading = true 时显示骨架屏
}
/** 延迟初始化非关键服务 */
private async initServicesLazy(): Promise<void> {
// 延迟 2 秒后再初始化统计服务等非关键服务
setTimeout(async () => {
// await AnalyticsService.getInstance().init();
// await PushService.getInstance().init();
console.info('[Startup] 延迟服务初始化完成');
}, 2000);
}
private async loadWelcomeText(): Promise<void> {
// 从缓存或默认值立即返回
this.welcomeText = '欢迎使用节气通'; // 默认值,瞬间显示
// 后台异步更新真实值
// const realText = await I18nService.getString('welcome');
// if (realText) this.welcomeText = realText;
}
private async loadBannerData(): Promise<void> {
try {
// this.bannerData = await ApiService.getBanners();
this.isLoading = false;
} catch (e) {
this.isLoading = false;
}
}
build() {
Stack({ alignContent: Alignment.Top }) {
if (this.isLoading) {
// 骨架屏:让用户感知"正在加载"
this.buildSkeleton()
} else {
Scroll() {
Column({ space: 16 }) {
// 欢迎语
Text(this.welcomeText)
.fontSize(18)
.fontWeight(FontWeight.Medium)
// 轮播区
Swiper() {
ForEach(this.bannerData, (item: BannerItem) => {
// ...
})
}
.autoPlay(true)
.interval(3000)
// 其他内容区块...
}
.padding(16)
}
.scrollBar(BarState.Off)
}
}
.width('100%')
.height('100%')
}
@Builder
buildSkeleton(): void {
Column({ space: 14 }) {
// 标题骨架
Row().width('60%').height(20).backgroundColor('#F0F0F0').borderRadius(4)
// 轮播骨架
Row().width('100%').height(160).backgroundColor('#F0F0F0').borderRadius(8)
// 内容块骨架
ForEach([1, 2, 3], (_: number) => {
Column({ space: 8 }) {
Row().width('80%').height(14).backgroundColor('#F0F0F0').borderRadius(4)
Row().width('100%').height(14).backgroundColor('#F0F0F0').borderRadius(4)
Row().width('90%').height(14).backgroundColor('#F0F0F0').borderRadius(4)
}
})
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
}
}
步骤5: 性能监控与工具
HiLog 性能埋点
typescript
// utils/PerfTracker.ets
import hilog from '@ohos.hilog';
class PerfTracker {
private static instance: PerfTracker;
private markers: Map<string, number> = new Map();
static getInstance(): PerfTracker {
if (!PerfTracker.instance) {
PerfTracker.instance = new PerfTracker();
}
return PerfTracker.instance;
}
/** 开始计时 */
startMark(name: string): void {
this.markers.set(name, Date.now());
hilog.info(0x0000, 'PerfTracker', `[START] ${name}`);
}
/** 结束计时并打印耗时 */
endMark(name: string): void {
const start = this.markers.get(name);
if (start) {
const cost = Date.now() - start;
hilog.info(0x0000, 'PerfTracker',
`[END] ${name} 耗时: ${cost}ms`);
// 超过阈值告警
if (cost > 3000) {
hilog.warn(0x0000, 'PerfTracker',
`[WARN] ${name} 耗时过长: ${cost}ms (>3000ms阈值)`);
}
this.markers.delete(name);
}
}
/** 包装异步函数自动计时 */
async trackAsync<T>(name: string, fn: () => Promise<T>): Promise<T> {
this.startMark(name);
try {
return await fn();
} finally {
this.endMark(name);
}
}
}
// 使用方式
const perf = PerfTracker.getInstance();
perf.startMark('loadSolarTerms');
const data = await loadData();
perf.endMark('loadSolarTerms');
// 或用 trackAsync 自动包裹
const result = await perf.trackAsync('fetchArticles', () =>
ApiService.fetchArticles()
);
架构总览
┌──────────────────────────────────────────────┐
│ 性能优化体系 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 渲染优化 │ │ 内存优化 │ │ 加载优化 │ │
│ │ │ │ │ │ │ │
│ │ LazyFor │ │ PixelMap │ │ 图片缓存 │ │
│ │ Each │ │ release │ │ 分页加载 │ │
│ │ @Reusable│ │ 循环引用 │ │ 延迟初始化 │ │
│ │ 条件渲染 │ │ 大对象释放 │ │ 骨架屏 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ PerfTracker 监控层 │ │
│ │ startMark → endMark → 日志输出 │ │
│ └──────────────────────────────────────┘ │
│ │
├──────────────────────────────────────────────┤
│ DevEco Profiler │
│ CPU / 内存 / GPU / 网络 / 能耗 可视化分析 │
└──────────────────────────────────────────────┘
优化优先级矩阵:
影响大 影响小
↑ ↑
┌──────────────┐ ┌──────────────┐
高 │ LazyForEach │ │ 代码精简 │
实 │ 图片多级缓存 │ │ 日志优化 │
施 │ 启动延迟加载 │ │ │
难 │ │ │ │
度 ├──────────────┤ ├──────────────┤
│ PixelMap释放 │ │ 字符串优化 │
低 │ 内存监控 │ │ 条件编译 │
│ 骨架屏 │ │ │
└──────────────┘ └──────────────┘
先做左上角(影响大+实施易)的项目
关键注意事项
1. LazyForEach 的 key 必须稳定唯一
typescript
// ❌ 错误:用 index 做 key,增删数据时会导致复用错乱
(term, index) => `${index}`
// ✓ 正确:用数据的唯一标识做 key
(term, index) => `${term.id}`
// ✓ 如果没有 id,用稳定的业务字段组合
(term, index) => `${term.name}_${term.solarDate}`
2. @State 变更触发全量重建要谨慎
@State 装饰的变量变更会导致整个组件重新执行 build()。对于频繁更新的变量:
typescript
// ❌ 频繁更新的计数器放在父组件
@State second: number = 0; // 每秒更新 → 整个页面 rebuild
// ✓ 将频繁更新的部分拆成子组件,用 @Prop 或 @Link 传递
// 子组件内部的变化不会触发父组件 rebuild
3. 图片不要全部用原图尺寸
手机屏幕宽度约 360-414dp(逻辑像素),图片实际需要:
- 缩略图:~360px 宽即可
- 详情大图:~750px 宽足够(2x Retina)
- 原图(2000px+)纯属浪费带宽和内存
4. aboutToDisappear 中必须清理资源
typescript
aboutToDisappear(): void {
// 清理清单:
// 1. 定时器 (clearInterval / clearTimeout)
// 2. 事件订阅 (off / removeEventListener)
// 3. PixelMap (.release())
// 4. 动画控制器 (.stop())
// 5. 大数组 / 大对象 (置 null)
}
5. 避免在 build() 中进行计算
build() 方法会在每次状态变更时调用,应保持轻量:
typescript
// ❌ 在 build 中做复杂计算
build() {
Text(this.computeHeavyResult()) // 每次 rebuild 都重算
}
// ✓ 在数据变更时预计算好
@State displayValue: string = '';
someMethod(): void {
this.displayValue = this.computeHeavyResult(); // 只算一次
}
build() {
Text(this.displayValue) // 直接读取
}
最佳实践清单
在进行性能优化时,请逐项检查:
- 长列表使用 LazyForEach + 自定义 DataSource
- LazyForEach 的 key 生成函数使用稳定唯一的标识符
- 不在可视区域的内容使用条件渲染 (
if) 延迟创建 - 图片使用 OptimizedImage 组件(带占位/错误态/缓存)
- 图片尺寸适配屏幕(不超过 2x 设备分辨率)
- PixelMap 使用后在 finally 块中 release()
- 定时器和事件监听在 aboutToDisappear 中清理
- 频繁更新的状态变量放在最小范围的组件内
- build() 方法中不含复杂计算或耗时操作
- 非关键服务和数据延迟到首屏渲染后加载
- 首屏使用骨架屏替代空白等待
- PerfTracker 埋点覆盖关键路径(启动/加载/切换)