《鸿蒙原生应用开发实战》第二篇: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 的优势

  1. 代码复用:同一卡片在场景列表和收藏列表中可以复用
  2. 参数化:通过参数传递数据,灵活适配不同场景
  3. 链式调用:可以在 builder 中直接链式配置样式

@Builder 与自定义组件的选择

对比 @Builder 自定义 @Component
状态管理 无独立状态 有独立 @State
复用范围 只能在当前结构体中使用 可导出跨文件使用
性能 轻量,无额外开销 略微重一些
适用场景 简单 UI 片段、卡片 复杂交互、独立功能模块

六、数据流设计模式总结

整个应用的数据流设计如下:

SceneData.ets(数据仓库)
  ├── SceneItem 接口(类型定义)
  ├── 8 个场景对象(数据实例)
  ├── 3 个查询函数(数据访问层)
  └── FAV_KEY 常量(数据键名)
         │
         ▼
  AppStorage(全局状态层)
         │
    ┌────┼────┬────┬────┐
    ▼    ▼    ▼    ▼    ▼
  Index ScenePage DetailPage FavPage ProfilePage
  (页面UI层,通过 @State 驱动)

关键原则

  1. 数据与 UI 分离:所有数据集中在 SceneData.ets,UI 页面只负责展示
  2. 单向数据流:数据从 Model → @State → UI,用户交互 → 回调 → 更新 Model
  3. 共享状态集中管理:跨页面数据用 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 中初始化


在这里插入图片描述

总结

本篇我们学习了:

  1. ✅ 数据模型定义与 ArkTS 严格模式规范
  2. ✅ @State 装饰器驱动 UI 更新
  3. ✅ AppStorage 全局状态共享实现收藏功能
  4. ✅ @Builder 组件化 UI 复用
  5. ✅ 数据流设计模式与最佳实践

状态管理是 ArkTS 开发的核心技能,掌握好这些机制,后续开发就会非常顺畅。下一篇我们将进入 UI 层面,看看如何用 ArkTS 实现沉浸式的光影视觉效果。

下一篇预告:沉浸式 UI 设计与组件化开发 —— 渐变背景、毛玻璃效果、光影动画实战

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐