《鸿蒙原生应用开发实战》第二篇:ArkTS 数据模型与状态管理
《鸿蒙原生应用开发实战》第二篇:ArkTS 数据模型与状态管理
前言
在上一篇中,我们搭建了项目的框架和路由体系。本篇将深入 ArkTS 的数据模型设计和状态管理机制。数据层是一个应用的灵魂,如何组织数据结构、如何管理状态变化、如何在页面之间共享数据,这些都是开发者必须掌握的技能。
本文将涵盖:
- 数据模型定义与接口设计
- 严格模式下的对象字面量规范
- @State、@Builder 装饰器详解
- AppStorage 全局数据共享
- 数据流设计模式
一、数据模型定义
场景数据模型(SceneData.ets)
我们的应用有 8 个沉浸式场景,归属 5 个分类。首先定义 SceneItem 接口:
// model/SceneData.ets
export interface SceneItem {
id: number; // 唯一标识
name: string; // 场景名称
desc: string; // 简短描述(卡片展示)
detail: string; // 详细描述(详情页展示)
category: string; // 分类:晨光/森林/海洋/夕阳/星夜
colors: string[]; // 主题色数组(3个颜色值)
sound: string; // 推荐白噪音名称
duration: number; // 建议体验时长(分钟)
}
数据组织
const SCENE_1: SceneItem = {
id: 1,
name: '黎明破晓',
desc: '金色的曙光穿透云层,唤醒沉睡的大地',
detail: '黎明时分,第一缕阳光划破天际,将天空染成金色与绯红交织的画卷...',
category: '晨光',
colors: ['#FF6B35', '#F7C948', '#FFF4E0'],
sound: '清晨鸟鸣',
duration: 15
};
// ... 共 8 个场景对象
严格模式下的对象字面量陷阱
ArkTS 严格模式下有一个重要的约束:对象字面量必须有显式类型声明(arkts-no-untyped-obj-literals 规则)。
正确做法:
// ✅ 正确:为每个对象变量声明类型
const SCENE_1: SceneItem = { /* ... */ };
const SCENE_2: SceneItem = { /* ... */ };
// ...
// ✅ 导出时也显式声明类型
const ALL_SCENES: SceneItem[] = [
SCENE_1, SCENE_2, SCENE_3, SCENE_4,
SCENE_5, SCENE_6, SCENE_7, SCENE_8
];
// ❌ 错误:数组字面量无法推断类型
const ALL_SCENES = [
{ id: 1, name: '黎明破晓', ... }, // 编译报错!
{ id: 2, name: '朝露晨光', ... }
];
这就是为什么我们要先声明独立变量(SCENE_1: SceneItem),再组装成数组的原因。
二、分类体系设计
场景分为 5 个分类,应用首页和场景列表页都需要用到:
export const CATEGORIES: string[] = ['全部', '晨光', '森林', '海洋', '夕阳', '星夜'];
提供三个查询函数:
// 获取所有场景
export function getScenes(): SceneItem[] {
return ALL_SCENES;
}
// 按 ID 查找单个场景
export function getSceneById(id: number): SceneItem | undefined {
return ALL_SCENES.find(item => item.id === id);
}
// 按分类筛选场景
export function getScenesByCategory(category: string): SceneItem[] {
if (category === '全部' || category === '') {
return ALL_SCENES;
}
return ALL_SCENES.filter(item => item.category === category);
}
这种设计简洁清晰,数据与 UI 完全解耦。如果需要后端数据,只需要将查询函数改为异步请求,页面代码无需改动。
三、@State 装饰器 —— 组件的状态驱动
ArkTS 中的 @State 是核心状态管理装饰器。当被 @State 修饰的变量发生变化时,UI 会自动重新渲染。
基本用法
@Component
struct ScenePage {
@State scenes: SceneItem[] = getScenes(); // 场景列表 → 变化时刷新网格
@State selectedCategory: string = '全部'; // 选中分类 → 变化时刷新筛选
build() {
Column() {
// 分类标签点击 → 更新 selectedCategory → 触发 UI 重绘
ForEach(CATEGORIES, (cat: string) => {
Text(cat)
.onClick(() => {
this.selectedCategory = cat; // 更新状态
this.scenes = getScenesByCategory(cat); // 更新数据
})
})
}
}
}
@State 的更新机制
// 方式1:直接赋值(基本类型)
@State name: string = '黎明破晓';
this.name = '星空璀璨'; // ✅ UI 更新
// 方式2:数组整体替换(推荐)
@State scenes: SceneItem[] = [];
this.scenes = getScenesByCategory('海洋'); // ✅ UI 更新
// 方式3:数组方法(需要确保引用变化)
// ⚠️ 如果只 push/splice 不重新赋值,UI 不会更新
this.scenes.push(newItem); // ❌ UI 未必更新
this.scenes = [...this.scenes, newItem]; // ✅ 新数组触发更新
@State 的生命周期
@State 变量在组件创建时初始化,在组件销毁时释放。如果需要在页面出现时重新加载数据,使用 aboutToAppear 生命周期:
@Component
struct FavPage {
@State favScenes: SceneItem[] = [];
@State favCount: number = 0;
// 页面出现前调用 —— 适合加载数据
aboutToAppear(): void {
this.loadFavScenes();
}
}
四、AppStorage —— 全局状态共享
当需要在不同页面之间共享数据时,AppStorage 是最佳选择。它是应用级的键值存储,所有页面都可以读写。
初始化与使用
// 在首页初始化收藏列表
aboutToAppear(): void {
if (!AppStorage.has(FAV_KEY)) {
AppStorage.set<number[]>(FAV_KEY, []);
}
}
// 在任意页面读取
const favList: number[] = AppStorage.get<number[]>(FAV_KEY) || [];
// 写入更新
AppStorage.set<number[]>(FAV_KEY, favList);
收藏功能实战
我们定义 FAV_KEY 常量:
export const FAV_KEY: string = 'fav_scenes';
收藏的数据流:
用户点击收藏 ❤️
↓
toggleFav() → 更新 AppStorage
↓
DetailPage 显示收藏状态
↓
FavPage 在 aboutToAppear 中读取 AppStorage → 展示收藏列表
↓
ProfilePage 在 aboutToAppear 中读取 AppStorage → 展示收藏数量
DetailPage 中的收藏切换逻辑:
// 检查是否已收藏
checkFavStatus(): void {
const favList: number[] = AppStorage.get<number[]>(FAV_KEY) || [];
this.isFav = this.scene ? favList.indexOf(this.scene.id) >= 0 : false;
}
// 切换收藏
toggleFav(): void {
let favList: number[] = AppStorage.get<number[]>(FAV_KEY) || [];
const idx = favList.indexOf(this.scene!.id);
if (idx >= 0) {
favList.splice(idx, 1); // 取消收藏
this.isFav = false;
} else {
favList.push(this.scene!.id); // 添加收藏
this.isFav = true;
}
AppStorage.set<number[]>(FAV_KEY, favList);
}
为什么选择 AppStorage 而不是本地文件?
| 方案 | 读写速度 | 跨页面 | 持久化 | 使用场景 |
|---|---|---|---|---|
| @State | 瞬间 | ❌ 仅当前组件 | ❌ | 组件内部状态 |
| AppStorage | 瞬间 | ✅ 所有页面 | ❌ 重启丢失 | 运行时全局状态 |
| Preferences | 毫秒级 | ✅ | ✅ | 用户配置、收藏持久化 |
| 数据库 | 取决于数据量 | ✅ | ✅ | 大量结构化数据 |
注意:AppStorage 存入的数组是引用,修改数组元素后必须重新
set()才能触发 UI 更新。
五、@Builder 装饰器 —— 组件化的利器
@Builder 是 ArkTS 中定义可复用 UI 片段的语法,类似于其他框架中的函数组件。
基础用法
@Builder
SceneCard(item: SceneItem) {
Column() {
Text(item.name).fontSize(18).fontColor(Color.White);
Text(item.desc).fontSize(12).fontColor($r('app.color.text_secondary'));
// ...
}
.borderRadius(16)
.onClick(() => {
router.pushUrl({
url: 'pages/DetailPage',
params: { sceneId: item.id }
});
})
}
@Builder 的优势
- 代码复用:同一卡片在场景列表和收藏列表中可以复用
- 参数化:通过参数传递数据,灵活适配不同场景
- 链式调用:可以在 builder 中直接链式配置样式
@Builder 与自定义组件的选择
| 对比 | @Builder | 自定义 @Component |
|---|---|---|
| 状态管理 | 无独立状态 | 有独立 @State |
| 复用范围 | 只能在当前结构体中使用 | 可导出跨文件使用 |
| 性能 | 轻量,无额外开销 | 略微重一些 |
| 适用场景 | 简单 UI 片段、卡片 | 复杂交互、独立功能模块 |
六、数据流设计模式总结
整个应用的数据流设计如下:
SceneData.ets(数据仓库)
├── SceneItem 接口(类型定义)
├── 8 个场景对象(数据实例)
├── 3 个查询函数(数据访问层)
└── FAV_KEY 常量(数据键名)
│
▼
AppStorage(全局状态层)
│
┌────┼────┬────┬────┐
▼ ▼ ▼ ▼ ▼
Index ScenePage DetailPage FavPage ProfilePage
(页面UI层,通过 @State 驱动)
关键原则:
- 数据与 UI 分离:所有数据集中在 SceneData.ets,UI 页面只负责展示
- 单向数据流:数据从 Model → @State → UI,用户交互 → 回调 → 更新 Model
- 共享状态集中管理:跨页面数据用 AppStorage,避免参数层层传递
七、踩坑记录
坑1:数组更新不触发 UI 重绘
// ❌ 直接修改数组元素
this.scenes[0].name = '新名字'; // UI 不变
// ✅ 替换整个数组
const newScenes = [...this.scenes];
newScenes[0] = { ...newScenes[0], name: '新名字' };
this.scenes = newScenes;
坑2:对象字面量编译报错
现象:编译错误 arkts-no-untyped-obj-literals
解决:给每个对象变量显式声明类型,不要用类型推断
坑3:AppStorage 存数组后读取为空
原因:AppStorage key 未初始化时 get() 返回 undefined
解决:用 || [] 兜底,并在首页的 aboutToAppear 中初始化

总结
本篇我们学习了:
- ✅ 数据模型定义与 ArkTS 严格模式规范
- ✅ @State 装饰器驱动 UI 更新
- ✅ AppStorage 全局状态共享实现收藏功能
- ✅ @Builder 组件化 UI 复用
- ✅ 数据流设计模式与最佳实践
状态管理是 ArkTS 开发的核心技能,掌握好这些机制,后续开发就会非常顺畅。下一篇我们将进入 UI 层面,看看如何用 ArkTS 实现沉浸式的光影视觉效果。
下一篇预告:沉浸式 UI 设计与组件化开发 —— 渐变背景、毛玻璃效果、光影动画实战
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)