news 2026/6/17 17:48:32

鸿蒙 RecommendData 这类卡片数据层应该怎么组织

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
鸿蒙 RecommendData 这类卡片数据层应该怎么组织

适合谁看

  • 想写可维护鸿蒙卡片数据层的人

  • 正在做每日推荐、排行榜、轮播类鸿蒙卡片的人

  • 不想把数据硬写进鸿蒙 Ability 文件的人

问题背景

鸿蒙卡片代码很容易越写越乱的一个原因是:

  • Ability 管生命周期

  • Ability 还管数据

  • Ability 还管兜底

  • Ability 还管资源校验

最后所有逻辑都挤在同一个文件里,改一个数据要动 Ability,改一个兜底要动 Ability,改一个资源名也要动 Ability。

项目中的真实场景

食界探味当前把鸿蒙卡片数据层单独放在:

  • app/ohos/entry/src/main/ets/formability/RecommendData.ets

对应的 Ability 只负责消费它:

// DailyRecommendFormAbility.ets import { getRecommendOfToday, resolveImageResName } from './RecommendData'; onAddForm(want: Want): formBindingData.FormBindingData { const item = getRecommendOfToday(); return formBindingData.createFormBindingData({ dishName: item.name, dishImage: resolveImageResName(item.imageResName), // ... }); }

Ability 不关心数据从哪来、怎么选、怎么校验,只关心"拿数据 → 绑定到卡片"。

核心实现

一、RecommendData.ets 的完整结构

// 1. 数据结构定义 export interface RecommendItem { id: string; // 菜品 ID(用于点击跳转) name: string; // 菜名 region: string; // 地区 imageResName: string; // 鸿蒙图片资源名 highlight: string; // 口味亮点 summary: string; // 一句话介绍 } // 2. 推荐列表(10 道菜品) const RECOMMEND_LIST: RecommendItem[] = [ { id: 'beef-curry', name: '牛肉咖喱', region: '印度 · 亚洲', imageResName: 'dish_beef_curry', highlight: '浓郁香料', summary: '椰香与香料层层叠起,入口热烈又厚实。' }, { id: 'sukiyaki', name: '寿喜锅', region: '日本 · 亚洲', imageResName: 'dish_sukiyaki', highlight: '鲜甜酱香', summary: '牛肉、蔬菜与寿喜烧汁一起慢慢煮到刚好。' }, // ... 共 10 道菜品 ]; // 3. 兜底数据 const FALLBACK_ITEM: RecommendItem = { id: 'fallback', name: '环球美食', region: '世界', imageResName: 'dish_fallback', highlight: '今天吃什么', summary: '打开食界探味,挑一道想去认识的新菜。', }; // 4. 图片资源白名单 const VALID_IMAGE_RES_NAMES: Set<string> = new Set( RECOMMEND_LIST.map((item) => item.imageResName).concat(FALLBACK_ITEM.imageResName) ); // 5. 今日选择算法 export function getRecommendOfToday(): RecommendItem { ... } // 6. 兜底获取 export function getFallbackItem(): RecommendItem { ... } // 7. 图片资源校验 export function resolveImageResName(imageResName: string): string { ... }

这个文件承担了 7 类职责,每一类都值得单独分析。

二、职责 1:定义卡片数据结构

export interface RecommendItem { id: string; name: string; region: string; imageResName: string; highlight: string; summary: string; }

通过RecommendItem明确了卡片需要的 6 个字段。这让数据项结构先稳定下来。

字段设计的考量:

字段

类型

用途

为什么需要

id

string

点击跳转到菜品详情页

卡片点击需要传参数

name

string

卡片上显示菜名

核心展示信息

region

string

卡片上显示地区

帮助用户判断兴趣

imageResName

string

鸿蒙图片资源名

卡片需要显示图片

highlight

string

口味亮点标签

吸引用户点击

summary

string

一句话介绍

补充信息,帮助决策

这个结构既不过于复杂(6 个字段),也不过于简单(包含了点击跳转需要的id)。

三、职责 2:维护推荐列表和兜底项

const RECOMMEND_LIST: RecommendItem[] = [ // 10 道菜品... ]; const FALLBACK_ITEM: RecommendItem = { id: 'fallback', name: '环球美食', region: '世界', imageResName: 'dish_fallback', highlight: '今天吃什么', summary: '打开食界探味,挑一道想去认识的新菜。', };

RECOMMEND_LIST是正常推荐数据,FALLBACK_ITEM是兜底数据。

兜底项的价值:

场景

没有兜底

有兜底

列表为空

卡片显示空白

显示"环球美食"

图片资源不存在

卡片渲染崩溃

显示兜底图片

数据异常

用户看到异常

用户看到正常兜底

鸿蒙卡片比应用页面更怕显示异常——因为卡片在桌面上,异常会一直显示,用户无法刷新。

四、职责 3:图片资源校验

const VALID_IMAGE_RES_NAMES: Set<string> = new Set( RECOMMEND_LIST.map((item) => item.imageResName).concat(FALLBACK_ITEM.imageResName) ); export function resolveImageResName(imageResName: string): string { if (!imageResName || !VALID_IMAGE_RES_NAMES.has(imageResName)) { return FALLBACK_ITEM.imageResName; } return imageResName; }

VALID_IMAGE_RES_NAMES是鸿蒙图片资源的白名单。resolveImageResName会检查传入的资源名是否在白名单中,如果不在就返回兜底图片。

这个设计的好处:

  1. 防止资源不存在导致崩溃— 鸿蒙卡片如果引用了不存在的$r()资源,会直接崩溃

  2. 新增菜品时只需加白名单— 不需要改校验逻辑

  3. 兜底行为明确— 用户永远看不到空白图片

五、职责 4:今日选择算法

export function getRecommendOfToday(): RecommendItem { if (RECOMMEND_LIST.length === 0) return FALLBACK_ITEM; const now = new Date(); const dateNum = now.getFullYear() * 10000 + (now.getMonth() + 1) * 100 + now.getDate(); const index = dateNum % RECOMMEND_LIST.length; return RECOMMEND_LIST[index]; }

日期轮询算法:用年月日生成数字,对列表长度取模。

日期

dateNum

index (mod 10)

菜品

2025-01-15

20250115

5

牛肉塔可

2025-01-16

20250116

6

石锅拌饭

2025-01-17

20250117

7

班尼迪克蛋

这个算法的特点:

  • 同一天内结果一致— 所有卡片展示同一道菜

  • 不同天自动切换— 不需要手动更新

  • 列表为空时兜底— 返回FALLBACK_ITEM

  • 不依赖网络— 纯本地计算

六、职责 5:兜底获取

export function getFallbackItem(): RecommendItem { return FALLBACK_ITEM; }

单独导出兜底项,方便其他地方使用(比如卡片 UI 的默认值)。

七、数据层和 Ability 的分工

RecommendData.ets(数据层) │ ├─ RecommendItem ← 数据结构 ├─ RECOMMEND_LIST[] ← 推荐列表 ├─ FALLBACK_ITEM ← 兜底数据 ├─ VALID_IMAGE_RES_NAMES ← 资源白名单 ├─ getRecommendOfToday() ← 选择算法 ├─ getFallbackItem() ← 兜底获取 └─ resolveImageResName() ← 资源校验 DailyRecommendFormAbility.ets(Ability 层) │ ├─ onAddForm() ← 消费数据层 ├─ onUpdateForm() ← 消费数据层 └─ onRemoveForm() ← 清理资源

Ability 只关心"拿数据 → 绑定到卡片",不关心数据从哪来、怎么选、怎么校验。

八、为什么这种组织方式值得复用

好处

说明

职责清晰

数据层管内容,Ability 管生命周期

易于测试

数据层可以独立测试,不需要启动 Ability

易于扩展

新增菜品只需加 RECOMMEND_LIST

易于维护

改数据不影响 Ability,改 Ability 不影响数据

兜底完善

资源异常时不会崩溃

关键代码位置

文件

作用

app/ohos/entry/src/main/ets/formability/RecommendData.ets

鸿蒙卡片数据层(本文核心)

app/ohos/entry/src/main/ets/formability/DailyRecommendFormAbility.ets

消费数据层

数据层职责全景图

RecommendData.ets │ ├─ 接口定义 │ └─ RecommendItem { id, name, region, imageResName, highlight, summary } │ ├─ 数据存储 │ ├─ RECOMMEND_LIST[] ← 10 道菜品 │ ├─ FALLBACK_ITEM ← 兜底数据 │ └─ VALID_IMAGE_RES_NAMES ← 图片资源白名单 │ ├─ 选择算法 │ └─ getRecommendOfToday() ← 日期轮询 │ ├─ 兜底获取 │ └─ getFallbackItem() ← 返回 FALLBACK_ITEM │ └─ 资源校验 └─ resolveImageResName() ← 白名单校验 + 兜底

常见坑

  • 把推荐列表直接写在 Ability 里— 改数据要动 Ability,职责混乱

  • 没有兜底数据项— 鸿蒙卡片异常时显示空白

  • 图片资源名不做校验— 鸿蒙$r()引用不存在的资源会崩溃

  • 每次更新时间策略都散落在多个地方— 应该集中管理

  • 数据结构不稳定— 新增字段时要改多个文件

  • 没有导出数据结构— 其他文件无法复用RecommendItem

可复用模板

鸿蒙卡片数据层模板

// 1. 数据结构 export interface CardItem { id: string; title: string; subtitle: string; imageRes: string; highlight: string; summary: string; } // 2. 数据列表 const ITEM_LIST: CardItem[] = [ { id: '1', title: '标题1', subtitle: '副标题1', imageRes: 'img_1', highlight: '亮点1', summary: '简介1' }, // ... ]; // 3. 兜底数据 const FALLBACK: CardItem = { id: 'fallback', title: '默认标题', subtitle: '默认副标题', imageRes: 'img_fallback', highlight: '默认亮点', summary: '默认简介', }; // 4. 资源白名单 const VALID_RES: Set<string> = new Set(ITEM_LIST.map(i => i.imageRes).concat(FALLBACK.imageRes)); // 5. 选择算法 export function getTodayItem(): CardItem { if (ITEM_LIST.length === 0) return FALLBACK; const now = new Date(); const dateNum = now.getFullYear() * 10000 + (now.getMonth() + 1) * 100 + now.getDate(); return ITEM_LIST[dateNum % ITEM_LIST.length]; } // 6. 资源校验 export function safeImage(name: string): string { if (!name || !VALID_RES.has(name)) return FALLBACK.imageRes; return name; }

数据层职责清单

鸿蒙卡片数据层应该包含: □ 数据结构定义(export interface) □ 数据列表(const LIST) □ 兜底数据(const FALLBACK) □ 资源白名单(const VALID_RES) □ 选择算法(getTodayItem) □ 资源校验(safeImage) □ 兜底获取(getFallback)

本篇总结

鸿蒙卡片数据层应该独立于 Ability 文件。RecommendData.ets这种组织方式很适合"每日推荐"类卡片:

  1. 数据结构稳定RecommendItem接口定义了 6 个字段

  2. 数据和兜底并存RECOMMEND_LIST+FALLBACK_ITEM

  3. 资源校验完善VALID_IMAGE_RES_NAMES白名单 +resolveImageResName兜底

  4. 选择算法轻量— 日期轮询,不依赖网络

  5. 职责分离— 数据层管内容,Ability 管生命周期

先把数据结构、兜底和轮换规则单独收好,后面维护会轻很多。这份代码很适合当鸿蒙卡片数据层组织的第一块样板。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/17 17:46:17

【全网首发】Claude Code v2.1.179 发布:硬核自愈断流危机,拯救 WSL2 鼠标滚动,Linux 沙箱大瘦身!

Anthropic 在 2026 年 6 月 16 日正式推送了 Claude Code 的 v2.1.179 版本。作为紧随参数级防火墙大修之后的又一次高频迭代&#xff0c;本版本深度聚焦于运行时容灾、多环境终端兼容性&#xff08;WSL2 开发者重大利好&#xff09;以及极端沙箱配置下的系统稳定性。研发团队以…

作者头像 李华
网站建设 2026/6/17 17:41:58

嵌入式开发中ELF链接器命令文件(LCF)的深度解析与实践指南

1. 项目概述与核心价值 在嵌入式开发&#xff0c;尤其是针对DSP56800E这类资源受限的处理器时&#xff0c;我们常常会陷入一种困境&#xff1a;代码编译通过了&#xff0c;但下载到芯片里要么跑飞&#xff0c;要么数据错乱。很多时候&#xff0c;问题的根源并不在算法逻辑&…

作者头像 李华
网站建设 2026/6/17 17:37:48

GitHunt筛选器深度解析:3大高效发现GitHub热门项目的核心技巧

GitHunt筛选器深度解析&#xff1a;3大高效发现GitHub热门项目的核心技巧 【免费下载链接】githunt Hunt the most starred projects on any date on GitHub 项目地址: https://gitcode.com/gh_mirrors/gi/githunt GitHunt是一款专业的GitHub项目智能筛选引擎&#xff0…

作者头像 李华
网站建设 2026/6/17 17:11:22

第五人格一键登录工具:告别扫码烦恼的终极解决方案

第五人格一键登录工具&#xff1a;告别扫码烦恼的终极解决方案 【免费下载链接】idv-login idv-login is an IdentityV login tool. 项目地址: https://gitcode.com/gh_mirrors/idv/idv-login 还在为每次登录《第五人格》都要掏出手机扫码而烦恼吗&#xff1f;idv-login…

作者头像 李华
网站建设 2026/6/17 17:09:49

如何让设备自动发现彼此?Avahi的零配置网络发现方案

如何让设备自动发现彼此&#xff1f;Avahi的零配置网络发现方案 【免费下载链接】avahi Avahi - Service Discovery for Linux using mDNS/DNS-SD -- compatible with Bonjour 项目地址: https://gitcode.com/gh_mirrors/ava/avahi 你是否曾为局域网中设备无法自动识别而…

作者头像 李华