rn_for_openharmony狗狗之家app实战-养护指南实现
摘要 该项目实现了一个狗狗养护指南功能模块,主要包含两级页面:分类列表页和详情页。数据采用嵌套结构组织,包含6大类24个养护知识点。列表页使用Card组件展示分类,通过flex布局实现子项两列排列,点击跳转时传递分类和标题参数。详情页通过useRoute Hook接收参数并显示对应内容。项目展示了React Native中常见的页面导航、参数传递和灵活布局的实现方法,代码结构清晰,注重用户体验细节
案例开源地址:https://atomgit.com/nutpi/rn_openharmony_dogimg
这个功能要做什么
养护指南是狗狗之家里比较实用的一个模块。养狗的人都知道,从喂食到洗澡,从训练到看病,要学的东西挺多的。这个模块就是把这些知识整理好,让用户方便查阅。
和上一篇的趣味知识不同,养护指南涉及到两级页面:列表页展示分类,点击进入详情页看具体内容。这就涉及到页面间的参数传递,是个很常见的场景。
数据结构设计
先看数据是怎么组织的:
const CARE = [
{icon: '🍖', title: '饮食营养', items: ['幼犬喂养', '成犬饮食', '老年犬营养', '禁忌食物']},
{icon: '🛁', title: '日常护理', items: ['洗澡技巧', '毛发护理', '指甲修剪', '耳朵清洁']},
每个分类是一个对象,包含 icon(图标)、title(分类名)、items(子项数组)。
这种嵌套结构比扁平数组更清晰。如果把所有子项放在一个数组里,还得额外存储它属于哪个分类,麻烦。
继续看其他分类:
{icon: '🏥', title: '健康医疗', items: ['疫苗接种', '常见疾病', '驱虫指南', '急救知识']},
{icon: '🎾', title: '训练教育', items: ['基础训练', '行为纠正', '社会化', '技能训练']},
健康医疗和训练教育,都是养狗必须了解的内容。
{icon: '🏠', title: '居家环境', items: ['狗窝选择', '安全防护', '玩具推荐', '出行准备']},
{icon: '❤️', title: '情感陪伴', items: ['理解狗狗', '情绪识别', '互动游戏', '分离焦虑']},
];
居家环境和情感陪伴,这两个分类容易被忽视,但其实很重要。
一共 6 个分类,每个分类 4 个子项,总共 24 个知识点。
列表页的实现
CarePage 负责展示分类列表:
import React from 'react';
import {View, Text, ScrollView, TouchableOpacity, StyleSheet} from 'react-native';
import {Header, Card} from '../../components';
import {useNavigation} from '../../hooks';
引入了 TouchableOpacity 处理点击,useNavigation 处理页面跳转。
组件函数开头获取导航方法:
export function CarePage() {
const {navigate} = useNavigation();
useNavigation 返回一个对象,解构出 navigate 方法。后面点击子项时会用到。
页面结构:
return (
<View style={s.container}>
<Header title="📖 养护指南" />
<ScrollView style={s.content}>
外层容器 + Header + ScrollView,这个结构前几篇都见过了。
分类卡片的渲染
用 map 遍历分类数组:
{CARE.map((c, i) => (
<Card key={i}>
<View style={s.head}>
<Text style={s.icon}>{c.icon}</Text>
<Text style={s.title}>{c.title}</Text>
</View>
每个分类渲染一个 Card。卡片头部是图标 + 标题,横向排列。
子项列表的渲染:
<View style={s.items}>
{c.items.map((item, j) => (
<TouchableOpacity
key={j}
style={s.item}
onPress={() => navigate('CareDetail', {title: item, category: c.title})}
>
<Text style={s.itemText}>{item}</Text>
</TouchableOpacity>
))}
</View>
</Card>
))}
这里有个嵌套的 map:外层遍历分类,内层遍历子项。
点击子项时调用 navigate,传入两个参数:
title:子项名称,如"幼犬喂养"category:所属分类,如"饮食营养"
这两个参数会传给详情页使用。
子项的布局技巧
子项要实现两列布局,看看样式怎么写的:
items: {flexDirection: 'row', flexWrap: 'wrap', marginHorizontal: -4},
item: {width: '50%', padding: 4},
关键点:
flexDirection: 'row'让子项横向排列flexWrap: 'wrap'允许换行,一行放不下就换到下一行width: '50%'每个子项占一半宽度,所以一行正好两个
负边距技巧:marginHorizontal: -4 配合子项的 padding: 4,可以让子项之间有间距,同时整体和卡片边缘对齐。
这是个常用的布局技巧,不用这个方法的话,第一个和最后一个子项会和边缘有多余的间距。
子项按钮的样式
itemText: {
backgroundColor: '#f5f5f5',
paddingVertical: 10,
borderRadius: 8,
fontSize: 14,
color: '#666',
textAlign: 'center'
},
- 灰色背景
#f5f5f5,和页面背景同色,但因为卡片是白色,所以能看出来 paddingVertical: 10上下内边距,让按钮有一定高度borderRadius: 8圆角textAlign: 'center'文字居中
整体效果是一个个灰色的小按钮,点击有反馈。
卡片头部的样式
head: {flexDirection: 'row', alignItems: 'center', marginBottom: 12},
icon: {fontSize: 28, marginRight: 10},
title: {fontSize: 17, fontWeight: '600', color: '#333'},
- 头部横向排列,垂直居中
- 图标字号 28,比较大,醒目
- 标题加粗,字号 17
marginBottom: 12 让头部和子项列表之间有间距。
详情页的参数接收
点击子项后跳转到 CareDetailPage,看看它怎么接收参数:
import React from 'react';
import {View, Text, ScrollView, StyleSheet} from 'react-native';
import {Header, Card} from '../../components';
import {useRoute} from '../../hooks';
引入了 useRoute Hook,用来获取路由参数。
参数获取:
export function CareDetailPage() {
const {params} = useRoute<{title: string; category: string}>();
useRoute 是泛型函数,传入参数类型 {title: string; category: string},这样 params 就有类型提示了。
useRoute 的实现原理
看看这个 Hook 是怎么写的:
export function useRoute<T = any>() {
const [route, setRoute] = useState(navigator.getRoute());
const [params, setParams] = useState<T>(navigator.getParams<T>());
用两个 state 分别存储当前路由名和参数。初始值从 navigator 获取。
订阅路由变化:
useEffect(() => {
const unsubscribe = navigator.subscribe((r, p) => {
setRoute(r);
setParams((p || {}) as T);
});
return unsubscribe;
}, []);
return {route, params};
}
当路由变化时,更新 state。p || {} 是兜底,防止参数为 undefined。
返回 {route, params},组件里解构使用。
详情内容的数据
详情页的内容也是写死的:
const CONTENT: Record<string, string> = {
'幼犬喂养': '幼犬需要高蛋白、高能量的食物来支持快速生长。建议每天喂食3-4次,选择专门的幼犬粮。',
'成犬饮食': '成年犬每天喂食1-2次即可。根据体型选择合适的狗粮,保持新鲜饮水。',
用 Record<string, string> 类型,key 是子项名称,value 是内容文本。
这种字典结构查找很方便,直接 CONTENT[params.title] 就能拿到对应内容。
更多内容:
'禁忌食物': '狗狗不能吃:巧克力、葡萄、洋葱、大蒜、木糖醇、酒精、咖啡因等。',
'洗澡技巧': '一般每2-4周洗一次澡。使用温水和狗狗专用沐浴露,注意保护眼睛和耳朵。',
'毛发护理': '根据毛发类型选择合适的梳子,长毛犬需要每天梳理,短毛犬每周2-3次。',
每条内容都是实用的养护知识,不是随便写的。
训练相关的内容:
'基础训练': '从简单的命令开始,如"坐下"、"趴下"。使用正向强化,用零食奖励。',
'行为纠正': '对于不良行为,要及时纠正但不要惩罚。通过转移注意力和正向引导来改变。',
'社会化': '在幼犬期进行社会化训练非常重要。让狗狗接触不同的人、动物和环境。',
强调正向训练,不提倡惩罚,这是现代训犬的主流理念。
详情页的渲染
export function CareDetailPage() {
const {params} = useRoute<{title: string; category: string}>();
const content = CONTENT[params.title] || '内容整理中...';
从字典里取内容,如果没找到就显示"内容整理中…"。这个兜底很重要,万一数据没对上,页面不会崩。
页面结构:
return (
<View style={s.container}>
<Header title={params.title} />
<ScrollView style={s.content}>
<Card>
<Text style={s.category}>{params.category}</Text>
<Text style={s.title}>{params.title}</Text>
<Text style={s.text}>{content}</Text>
</Card>
</ScrollView>
</View>
);
}
Header 的标题用参数里的 title,这样每个详情页的标题都不一样。
Card 里面三行内容:分类名、标题、正文。
详情页的样式
const s = StyleSheet.create({
container: {flex: 1, backgroundColor: '#f5f5f5'},
content: {flex: 1},
category: {fontSize: 13, color: '#D2691E', marginBottom: 6},
分类名用主题色 #D2691E,字号小一点,起到标签的作用。
title: {fontSize: 20, fontWeight: '600', color: '#333', marginBottom: 14},
text: {fontSize: 15, color: '#666', lineHeight: 26},
});
标题字号 20,加粗,是页面的视觉重点。
正文 lineHeight: 26 行高比较大,阅读长文本时更舒服。
页面跳转的参数传递
回顾一下参数传递的完整流程:
发送方(CarePage):
navigate('CareDetail', {title: item, category: c.title})
第一个参数是目标路由名,第二个参数是要传递的数据对象。
接收方(CareDetailPage):
const {params} = useRoute<{title: string; category: string}>();
通过 useRoute 获取参数,泛型指定参数类型。
这种模式在 App 开发中非常常见,列表页传 ID 或数据给详情页,详情页根据参数渲染内容。
为什么不用接口
这个模块的数据全部写死在代码里,没有用接口。原因和趣味知识页面类似:
- 内容相对固定,养护知识不会频繁变化
- 减少网络依赖,离线也能用
- 简化开发,不用处理加载和错误状态
如果后续内容越来越多,或者想做个性化推荐,再考虑接口化。
嵌套 map 的注意事项
CarePage 里用了嵌套的 map:
{CARE.map((c, i) => (
<Card key={i}>
...
{c.items.map((item, j) => (
<TouchableOpacity key={j}>
两层 map 都需要 key。外层用 i,内层用 j,不会冲突,因为它们在不同的层级。
如果数据有唯一 ID,用 ID 做 key 更好。这里数据是静态的,用索引没问题。
两列布局的其他实现方式
除了 flexWrap,还有其他方式实现两列布局:
FlatList 的 numColumns:
<FlatList data={items} numColumns={2} renderItem={...} />
FlatList 原生支持多列,但这里子项数量少,没必要用。
手动分组:
把数组按两个一组拆分,然后渲染每组。代码会复杂一些,一般不推荐。
flexWrap 是最简单直接的方式,推荐使用。
可以优化的地方
当前实现能用,但有改进空间:
内容太短:每个知识点只有一两句话,可以扩充成更详细的文章。
缺少图片:纯文字有点枯燥,加些配图会更好。
没有搜索:内容多了之后,用户可能想搜索特定主题。
没有收藏:用户可能想收藏常看的内容。
这些后续可以慢慢加,先把核心功能做好。
小结
养护指南模块涉及到两级页面和参数传递,是 App 开发中很典型的场景。
关键点回顾:
- 嵌套数据结构的设计,分类包含子项
flexWrap实现两列布局navigate传递参数,useRoute接收参数- 字典结构存储详情内容,方便查找
下一篇讲养护详情页的扩展,会加入更多内容和交互。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐




所有评论(0)