
【鸿蒙原生应用开发实战】第二篇数据模型与状态管理 — 彻底搞懂 ArkTS 的数据驱动机制前言在上一篇中我们搭建了项目架构并完成了首页开发。这一篇我们将深入 ArkTS 的核心 —数据模型定义与状态管理机制。这是整个应用的发动机数据模型决定了App能展示什么状态管理决定了UI如何响应数据变化。本文你将学到ArkTS 的interface与class的正确使用姿势静态工具类FavoriteManager的设计模式State状态管理的完整工作流程数据流在页面间的传递方式鸿蒙严格模式下的类型约束规则一、数据模型层设计思路在鸿蒙原生开发中数据模型是业务逻辑的起点。我们遵循数据驱动UI的理念所有的页面展示都由数据模型决定。宇宙探索App 的数据架构数据层CelestialData.ets ├── interface CelestialData ← 数据结构定义 ├── const CELESTIAL_LIST ← 静态数据源10个天体 └── class FavoriteManager ← 收藏管理工具类 ↑ 业务层各页面组件 ├── Index.ets ← 首页展示 ├── CelestialPage.ets ← 列表筛选 ├── DetailPage.ets ← 详情展示与收藏 ├── FavPage.ets ← 收藏管理 └── ProfilePage.ets ← 个人统计二、CelestialData 接口设计2.1 接口定义// entry/src/main/ets/model/CelestialData.etsexportinterfaceCelestialData{id:number;name:string;englishName:string;type:string;description:string;mass:string;diameter:string;distance:string;temperature:string;fact:string;color:string;isFavorite:boolean;}每个字段的设计考量字段类型用途示例值idnumber唯一标识用于路由传参和收藏管理1, 2, 3…namestring天体中文名“太阳”englishNamestring英文名增加国际感“Sun”typestring分类标签“恒星”、“行星”、“星系”descriptionstring详细描述文本“太阳是太阳系的中心天体…”massstring质量信息“1.989 × 10³⁰ kg”diameterstring直径信息“1,392,700 km”distancestring距地距离“1.496 × 10⁸ km”temperaturestring温度信息“表面 5,500°C”factstring趣味知识“太阳每秒钟将约400万吨物质转化为能量”colorstring主题色十六进制“#FF6B35”isFavoriteboolean收藏状态标记false2.2 完整数据源我们准备了10个天体数据覆盖了5个分类exportconstCELESTIAL_LIST:CelestialData[][{id:1,name:太阳,englishName:Sun,type:恒星,description:太阳是太阳系的中心天体占据了太阳系总质量的99.86%...,mass:1.989 × 10³⁰ kg,diameter:1,392,700 km,distance:1.496 × 10⁸ km1天文单位,temperature:表面 5,500°C / 核心 1,500万°C,fact:太阳每秒钟将约400万吨物质转化为能量...,color:#FF6B35,isFavorite:false},{id:2,name:水星,/* ... */color:#A0A0A0,isFavorite:false},{id:3,name:金星,/* ... */color:#E8C07A,isFavorite:false},{id:4,name:地球,/* ... */color:#4B7B8A,isFavorite:false},{id:5,name:火星,/* ... */color:#C1440E,isFavorite:false},{id:6,name:木星,/* ... */color:#D4A574,isFavorite:false},{id:7,name:土星,/* ... */color:#D4B896,isFavorite:false},{id:8,name:银河系,type:星系,color:#6B8EC4,isFavorite:false},{id:9,name:猎户座大星云,type:星云,color:#FF69B4,isFavorite:false},{id:10,name:黑洞,type:天文现象,color:#2D2D3D,isFavorite:false}];数据覆盖了恒星(1) 行星(6) 星系(1) 星云(1) 天文现象(1)足够支撑各个页面的分类筛选和展示。类型推断注意在 ArkTS 严格模式下数组字面量必须能被推断出类型。这里CELESTIAL_LIST显式标注为CelestialData[]字面量对象的结构也会被严格校验。三、FavoriteManager 收藏管理器收藏功能是整个App中最核心的交互我们用一个静态工具类来管理收藏状态。3.1 完整实现exportclassFavoriteManager{staticfavorites:number[][];// 切换收藏状态已收藏则取消未收藏则添加statictoggle(id:number):boolean{constindexFavoriteManager.favorites.indexOf(id);if(index0){FavoriteManager.favorites.splice(index,1);returnfalse;// 已取消收藏}else{FavoriteManager.favorites.push(id);returntrue;// 已添加收藏}}// 判断某个天体是否已收藏staticisFavorite(id:number):boolean{returnFavoriteManager.favorites.indexOf(id)0;}// 获取所有收藏的id列表staticgetAll():number[]{returnFavoriteManager.favorites;}// 清空所有收藏staticclear():void{FavoriteManager.favorites[];}// 获取收藏数量staticgetCount():number{returnFavoriteManager.favorites.length;}}3.2 设计模式分析为什么用静态类而不是全局变量封装性所有操作收藏的方法集中在一个类中逻辑内聚可维护性未来如果需要持久化比如用PersistentStorage或数据库只需修改FavoriteManager内部实现不影响调用方可测试性静态方法易于单元测试为什么不使用 State 全局状态管理这是一个篇幅适中的App页面间数据传递并不复杂。用静态类比引入状态管理库如 Redux / Pinia更轻量开发效率更高。3.3 可扩展设计如果你想让收藏数据持久化退出App后不丢失只需改造FavoriteManager// 持久化版本示例使用AppStorageexportclassFavoriteManager{StorageProp(favorites)staticfavorites:number[][];statictoggle(id:number):boolean{constindexthis.favorites.indexOf(id);if(index0){this.favorites.splice(index,1);AppStorage.setnumber[](favorites,[...this.favorites]);returnfalse;}else{this.favorites.push(id);AppStorage.setnumber[](favorites,[...this.favorites]);returntrue;}}// ...其他方法类似改造}四、State 状态管理深入理解4.1 State 的本质在 ArkTS 中State装饰的变量会被框架监视——当变量值变化时框架自动重新渲染依赖于该变量的UI部分。Statecategories:CategoryItem[][/* ... */];StatehotList:CelestialData[]CELESTIAL_LIST.slice(0,5);StaterecommendList:CelestialData[]CELESTIAL_LIST.slice(5,10);关键规则State变量变化 → 依赖该变量的UI自动刷新只有State变量变化才会触发刷新普通变量不会State是组件级状态不同组件实例的State相互独立4.2 数据驱动流程图用户操作点击收藏按钮 ↓ FavoriteManager.toggle(id) ← 修改数据层 ↓ State isFav ... ← 状态变量更新 ↓ 框架检测到 State 变化 ↓ 自动重新渲染 build() ← UI刷新4.3 实战中的状态管理场景场景一列表页收藏状态在CelestialCard组件中每个卡片有自己的收藏状态struct CelestialCard{item:CelestialData{/* ... */};StateisFav:booleanfalse;aboutToAppear():void{this.isFavFavoriteManager.isFavorite(this.item.id);}toggleFav():void{constnewStateFavoriteManager.toggle(this.item.id);this.isFavnewState;// 触发UI刷新this.item.isFavoritenewState;// 同步到数据对象}}当用户点击收藏按钮时toggleFav()被调用FavoriteManager.toggle()修改数据层this.isFav newState触发State刷新收藏图标从 ☆ 变为 ★或相反场景二详情页收藏struct DetailPage{Statedata:CelestialData{/* ... */};StateisFav:booleanfalse;aboutToAppear():void{// 从路由参数获取id找到对应天体数据constparamsrouter.getParams()asRecordstring,Object;if(paramsparams[id]!undefined){constidNumber(params[id]);for(leti0;iCELESTIAL_LIST.length;i){if(CELESTIAL_LIST[i].idid){this.dataCELESTIAL_LIST[i];break;}}}this.isFavFavoriteManager.isFavorite(this.data.id);}toggleFav():void{this.isFavFavoriteManager.toggle(this.data.id);this.data.isFavoritethis.isFav;}}场景三收藏列表页struct FavPage{StatefavList:CelestialData[][];onPageShow():void{this.loadFavorites();// 每次页面显示时刷新}loadFavorites():void{constfavIdsFavoriteManager.getAll();constresult:CelestialData[][];for(leti0;iCELESTIAL_LIST.length;i){if(favIds.indexOf(CELESTIAL_LIST[i].id)0){result.push(CELESTIAL_LIST[i]);}}this.favListresult;// 触发UI刷新}}注意onPageShowvsaboutToAppearaboutToAppear— 仅在组件首次创建时调用一次onPageShow— 每次页面显示包括从其他页面返回时都调用收藏列表页需要在每次返回时刷新数据所以用onPageShow。五、路由传参中的数据流5.1 路由传递参数// 从首页跳转到详情页router.pushUrl({url:pages/DetailPage,params:{id:this.item.id}});// 从首页跳转到分类列表router.pushUrl({url:pages/CelestialPage,params:{filterType:this.category.type}});5.2 目标页面接收参数// DetailPage 中aboutToAppear():void{constparamsrouter.getParams()asRecordstring,Object;if(paramsparams[id]!undefined){constidNumber(params[id]);// 根据id查找天体数据...}}// CelestialPage 中aboutToAppear():void{constparamsrouter.getParams()asRecordstring,Object;if(paramsparams[filterType]!undefined){this.filterTypeString(params[filterType]);this.activeTabthis.filterType;// 选中对应标签}this.applyFilter();}⚠️router.getParams()返回类型为Recordstring, Object需要用as断言后取值。取出的值可能需要转换为预期的类型如Number()、String()。5.3 整个数据流链路Index首页 │ 用户点击分类卡片 │ router.pushUrl({ url: pages/CelestialPage, params: { filterType: 行星 }}) ▼ CelestialPage分类列表 │ aboutToAppear() 读取 params.filterType │ State activeTab 行星 → applyFilter() 筛选 → 渲染列表 │ 用户点击天体卡片 │ router.pushUrl({ url: pages/DetailPage, params: { id: 4 }}) ▼ DetailPage详情页 │ aboutToAppear() 读取 params.id │ 遍历 CELESTIAL_LIST 找到 id4 → State data 地球数据 │ 用户点击收藏 → FavoriteManager.toggle(4) → State isFav 刷新六、ArkTS 严格模式注意事项HarmonyOS 6.1 的 ArkTS 编译器在默认开启的严格模式下有一些关键约束不遵守会导致编译失败。6.1 对象字面量必须显式类型// ❌ 错误 - 编译器无法推断字面量类型Statecategories[{name:行星,type:行星,icon:}];// ✅ 正确 - 显式标注类型interfaceCategoryItem{name:string;type:string;icon:string;}Statecategories:CategoryItem[][{name:行星,type:行星,icon:}];6.2 组件属性的默认值Componentstruct CelestialCard{// ❌ 错误 - 属性必须有默认值且类型必须标注item:CelestialData;// ✅ 正确 - 提供完整的默认对象item:CelestialData{id:0,name:,englishName:,type:,description:,mass:,diameter:,distance:,temperature:,fact:,color:#FFFFFF,isFavorite:false};}6.3 数组字面量推断// ❌ 错误 - 编译器无法推断字面量数组的类型constlist[{id:1,name:test}];// ✅ 正确 - 显式标注数组类型constlist:DataItem[][{id:1,name:test}];6.4 ForEach 的 key 函数在 ArkTS 中ForEach支持三个参数第三个是key 生成函数用于优化列表 diffForEach(this.infoItems,(item:InfoPair)InfoItem({label:item.label,value:item.value}),(item:InfoPair)item.label// key 生成函数)如果列表中没有重复项也可以省略第三个参数但当列表项可能变化时提供 key 可以提升渲染性能。七、本篇总结本篇我们深入探讨了✅数据模型设计—CelestialData接口和CELESTIAL_LIST常量数据✅FavoriteManager 工具类— 封装收藏逻辑面向未来可扩展✅State 状态管理— 理解数据驱动UI的核心机制✅路由传参数据流— 页面间的参数传递与接收✅严格模式约束— 对象字面量、组件属性、数组类型的正确写法核心思考鸿蒙 ArkTS 采用数据驱动UI范式我们不需要手动操作DOM或调用 setState()只需要修改State变量的值框架自动处理UI刷新。这让代码更简洁、更可预测。下篇预告我们将开发最重要的页面之一 — CelestialPage天体列表页。你将学到标签筛选的实现原理、ForEach 的多种渲染技巧以及 CCard 组件的完整设计。本篇涉及的文件entry/src/main/ets/model/CelestialData.ets— 数据模型所有页面文件都会引用这个模型