rn_for_openharmony狗狗之家app实战-养护详情实现
本文介绍了狗狗养护指南详情页的实现,重点包括: 详情页结构:使用字典存储各类养护知识内容,通过路由参数动态渲染不同内容 导航系统:自定义实现的Navigator类管理页面栈和路由跳转 关键技术点: 使用useRoute Hook获取路由参数 通过Record类型组织内容数据 实现订阅/通知机制进行状态管理 完整的页面栈操作(前进/后退) 该方案展示了如何在不依赖第三方库的情况下,实现一个完整的Re
案例开源地址:https://atomgit.com/nutpi/rn_openharmony_dogimg
承上启下
上一篇讲了养护指南的列表页,用户点击某个知识点后会跳转到详情页。这篇就来聊聊详情页的实现,重点是路由参数的接收和自定义导航器的设计。
详情页本身很简单,但背后的导航系统值得深入讲讲。为什么要自己写导航器?怎么实现页面栈管理?这些问题在这篇文章里都会涉及到。
详情页的完整代码
先看 CareDetailPage 的全貌:
import React from 'react';
import {View, Text, ScrollView, StyleSheet} from 'react-native';
import {Header, Card} from '../../components';
import {useRoute} from '../../hooks';
引入很简洁,就四个:React、RN 组件、自定义组件、自定义 Hook。
没有引入 useState 和 useEffect,因为这个页面没有内部状态,数据全靠路由参数。
内容数据的组织
详情内容用字典结构存储:
const CONTENT: Record<string, string> = {
'幼犬喂养': '幼犬需要高蛋白、高能量的食物来支持快速生长。建议每天喂食3-4次,选择专门的幼犬粮。',
'成犬饮食': '成年犬每天喂食1-2次即可。根据体型选择合适的狗粮,保持新鲜饮水。',
Record<string, string> 是 TypeScript 的工具类型,表示一个对象,key 和 value 都是字符串。
用知识点标题做 key,这样查找很方便:CONTENT['幼犬喂养'] 直接拿到内容。
继续看其他内容:
'老年犬营养': '老年犬新陈代谢减慢,需要低热量、高纤维的食物。可以选择老年犬专用粮。',
'禁忌食物': '狗狗不能吃:巧克力、葡萄、洋葱、大蒜、木糖醇、酒精、咖啡因等。',
老年犬和禁忌食物,都是养狗必须知道的。
日常护理相关:
'洗澡技巧': '一般每2-4周洗一次澡。使用温水和狗狗专用沐浴露,注意保护眼睛和耳朵。',
'毛发护理': '根据毛发类型选择合适的梳子,长毛犬需要每天梳理,短毛犬每周2-3次。',
'指甲修剪': '每2-4周检查一次指甲长度。使用专用指甲钳,注意不要剪到血线。',
'耳朵清洁': '每周检查耳朵,使用专用清洁液和棉球轻轻擦拭。',
这些都是日常护理的基本操作,新手铲屎官特别需要。
健康医疗部分:
'疫苗接种': '幼犬需要在6-8周开始接种疫苗,包括犬瘟热、细小病毒、狂犬病等。',
'常见疾病': '常见疾病包括皮肤病、肠胃问题、关节炎等。定期体检可以早期发现问题。',
'驱虫指南': '体内驱虫每3个月一次,体外驱虫每月一次。选择正规品牌的驱虫药。',
'急救知识': '学习基本的急救技能,如人工呼吸、止血包扎等。准备急救箱。',
疫苗和驱虫是必须的,急救知识关键时刻能救命。
训练教育部分:
'基础训练': '从简单的命令开始,如"坐下"、"趴下"。使用正向强化,用零食奖励。',
'行为纠正': '对于不良行为,要及时纠正但不要惩罚。通过转移注意力和正向引导来改变。',
'社会化': '在幼犬期进行社会化训练非常重要。让狗狗接触不同的人、动物和环境。',
'技能训练': '在掌握基础命令后,可以教授更多技能,如握手、翻滚、捡球等。',
强调正向强化,这是现代训犬的核心理念。
居家环境和情感陪伴:
'狗窝选择': '选择大小合适、材质舒适的狗窝。放置在安静、通风的位置。',
'安全防护': '收好电线、化学品、小物件等危险物品。安装宠物门栏。',
'理解狗狗': '学习狗狗的肢体语言,如摇尾巴、耳朵位置、眼神等。',
'分离焦虑': '逐渐训练狗狗独处能力。离开时不要过度告别,回来时也不要过度兴奋。',
};
分离焦虑是很多狗狗都有的问题,处理方法很重要。
组件函数的实现
export function CareDetailPage() {
const {params} = useRoute<{title: string; category: string}>();
用 useRoute 获取路由参数。泛型 <{title: string; category: string}> 指定参数类型,这样 params.title 和 params.category 都有类型提示。
获取内容:
const content = CONTENT[params.title] || '内容整理中...';
从字典里取内容,用 || 做兜底。如果 params.title 在字典里找不到,就显示"内容整理中…"。
这个兜底很重要。万一列表页和详情页的数据没对上,页面不会崩,用户也能看到提示。
页面渲染结构
return (
<View style={s.container}>
<Header title={params.title} />
Header 的标题用参数里的 title,每个详情页标题都不一样。比如点击"幼犬喂养"进来,Header 就显示"幼犬喂养"。
内容区域:
<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>
);
}
Card 里面三层内容:
- category:分类名,如"饮食营养",用主题色显示
- title:知识点标题,如"幼犬喂养",字号大,加粗
- text:正文内容
详情页的样式
const s = StyleSheet.create({
container: {flex: 1, backgroundColor: '#f5f5f5'},
content: {flex: 1},
容器撑满屏幕,背景浅灰。
文字样式:
category: {fontSize: 13, color: '#D2691E', marginBottom: 6},
title: {fontSize: 20, fontWeight: '600', color: '#333', marginBottom: 14},
text: {fontSize: 15, color: '#666', lineHeight: 26},
});
- category 字号 13,主题色,像个小标签
- title 字号 20,加粗,是视觉重点
- text 字号 15,行高 26,阅读舒适
lineHeight: 26 比默认行高大,多行文本读起来不累。
自定义导航器的设计
详情页能正常工作,离不开背后的导航系统。来看看 navigator.ts 是怎么实现的。
类型定义:
type Params = Record<string, any>;
type Listener = (route: string, params?: Params) => void;
Params是参数类型,key 是字符串,value 可以是任何类型Listener是监听函数类型,接收路由名和参数
Navigator 类的结构
class Navigator {
private route = 'Home';
private stack: Array<{route: string; params?: Params}> = [{route: 'Home'}];
private listeners = new Set<Listener>();
private params: Params = {};
四个私有属性:
- route:当前路由名,初始是 ‘Home’
- stack:页面栈,数组结构,存储历史记录
- listeners:监听器集合,用 Set 存储
- params:当前路由的参数
页面栈初始有一个元素 {route: 'Home'},表示首页。
订阅机制
subscribe(fn: Listener): () => void {
this.listeners.add(fn);
return () => {
this.listeners.delete(fn);
};
}
subscribe 方法添加监听器,返回取消订阅的函数。
用 Set 存储监听器有两个好处:
- 自动去重:同一个函数不会被添加两次
- 删除高效:Set 的 delete 是 O(1) 复杂度
通知所有监听器:
private notify() {
this.listeners.forEach(fn => fn(this.route, this.params));
}
遍历 Set,调用每个监听函数,传入当前路由和参数。
导航方法
前进导航:
navigate(route: string, params?: Params) {
this.route = route;
this.params = params || {};
this.stack.push({route, params});
this.notify();
}
四步操作:
- 更新当前路由名
- 更新当前参数(没传就用空对象)
- 把新页面压入栈
- 通知所有监听器
返回导航:
goBack(): boolean {
if (this.stack.length > 1) {
this.stack.pop();
const prev = this.stack[this.stack.length - 1];
this.route = prev.route;
this.params = prev.params || {};
this.notify();
return true;
}
return false;
}
先检查栈长度,大于 1 才能返回(不能把首页也弹出去)。
弹出当前页面,取栈顶元素作为新的当前页面,更新状态并通知。
返回 boolean 表示是否成功返回,调用方可以据此判断。
重置导航:
reset(route: string) {
this.route = route;
this.params = {};
this.stack = [{route}];
this.notify();
}
清空栈,只保留指定的页面。用于登录后跳转首页这种场景,不希望用户能返回到登录页。
辅助方法
getRoute() { return this.route; }
getParams<T>() { return this.params as T; }
canGoBack() { return this.stack.length > 1; }
}
getRoute获取当前路由名getParams获取当前参数,支持泛型指定类型canGoBack判断能否返回,Header 组件用这个决定是否显示返回按钮
导出单例:
export const navigator = new Navigator();
整个 App 共用一个 navigator 实例。
useRoute Hook 的实现
组件里用 useRoute 获取参数,看看它的实现:
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;
}, []);
组件挂载时订阅 navigator,路由变化时更新 state。
return unsubscribe 是清理函数,组件卸载时取消订阅,防止内存泄漏。
返回值:
return {route, params};
}
返回对象,组件里解构使用:const {params} = useRoute()。
为什么自己写导航器
你可能会问,React Navigation 不香吗,为什么要自己造轮子?
几个原因:
兼容性问题。试了几个主流导航库,在 OpenHarmony 上多少都有问题。与其花时间排查,不如自己写一个简单的。
需求简单。狗狗之家的导航需求不复杂,就是前进、后退、重置,不需要 Tab 导航、Drawer 导航这些高级功能。
可控性强。自己写的代码,出了问题好排查,想加功能也方便。
当然,如果项目复杂,还是建议用成熟的导航库,自己写容易漏掉边界情况。
页面栈的可视化理解
假设用户的操作路径是:首页 → 养护指南 → 幼犬喂养
页面栈的变化:
初始:[{route: 'Home'}]
点击养护指南:[{route: 'Home'}, {route: 'Care'}]
点击幼犬喂养:[{route: 'Home'}, {route: 'Care'}, {route: 'CareDetail', params: {title: '幼犬喂养', category: '饮食营养'}}]
点击返回:
返回一次:[{route: 'Home'}, {route: 'Care'}]
再返回:[{route: 'Home'}]
栈是后进先出的结构,最后进入的页面最先被弹出。
参数传递的完整链路
再梳理一下参数从发送到接收的完整过程:
1. 列表页发送参数
navigate('CareDetail', {title: item, category: c.title})
2. navigator 存储参数
this.params = params || {};
this.stack.push({route, params});
3. 详情页接收参数
const {params} = useRoute<{title: string; category: string}>();
4. 使用参数渲染
<Text>{params.title}</Text>
<Text>{CONTENT[params.title]}</Text>
整个链路清晰明了,数据流向单一。
类型安全的好处
用 TypeScript 的泛型指定参数类型:
useRoute<{title: string; category: string}>()
好处是:
- 编辑器提示:输入
params.会自动提示title和category - 类型检查:如果写错属性名,编译时就会报错
- 代码可读:一眼就知道这个页面需要哪些参数
小结
养护详情页本身代码不多,但背后的导航系统值得深入理解。
这篇文章讲了:
- 字典结构存储详情内容,查找方便
- useRoute Hook 获取路由参数
- Navigator 类的完整实现:订阅机制、页面栈管理、导航方法
- 参数传递的完整链路
自己实现导航器虽然简单,但涵盖了发布订阅、栈数据结构、TypeScript 泛型等知识点,是个不错的练手项目。
下一篇讲品种列表页,会涉及到网络请求和列表渲染的优化。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)