RN for OpenHarmony AnimeHub项目实战:热门排行页面开发

案例开源地址:https://atomgit.com/nutpi/Rn_openharmony_AnimeHub
热门排行页展示评分最高的动漫作品,是动漫爱好者发现优质内容的重要入口。这篇来讲热门排行页的实现,重点是排名显示和可复用的列表组件设计。
功能设计
热门排行页需要实现以下功能:
- 排名显示 - 每个动漫显示排名序号
- 列表展示 - 纵向列表,每项显示详细信息
- 分页加载 - 滚动到底部自动加载更多
- 动态标题 - 支持不同筛选条件的标题
这个页面和季度动漫页的结构类似,但有一个关键区别:使用列表布局而不是网格布局。排行榜更适合列表形式,因为需要显示排名序号,而且用户通常会从上到下依次浏览。
排行榜是内容型应用的核心功能之一。用户经常会问"有什么好看的动漫推荐",排行榜就是最好的答案。按评分排序的列表可以帮助用户快速找到高质量内容。
路由参数处理
从路由获取筛选条件和标题:
export const TopAnimeScreen = ({ navigation, route }: any) => {
const { filter = '', title = '热门排行' } = route.params || {};
参数说明:
filter- 筛选条件,可选,默认为空字符串title- 页面标题,可选,默认为"热门排行"route.params || {}- 防止 params 为 undefined
这个页面设计成可复用的。通过传入不同的
filter参数,可以显示不同类型的排行:
- 空字符串 - 综合排行
- ‘airing’ - 正在播出的动漫排行
- ‘upcoming’ - 即将上映的动漫排行
- ‘bypopularity’ - 按人气排行
- ‘favorite’ - 最受喜爱排行
默认值的设置很重要。
filter = ''和title = '热门排行'确保即使不传参数,页面也能正常工作。route.params || {}是双重保险,防止整个 params 对象为空时解构报错。
状态定义
和季度动漫页类似的状态结构:
const [animeList, setAnimeList] = useState<Anime[]>([]);
const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
状态说明:
animeList- 动漫列表数据loading- 首次加载状态loadingMore- 加载更多状态page- 当前页码hasMore- 是否还有更多数据
你会发现这个状态结构和季度动漫页几乎一样。这是因为分页列表的状态模式是固定的,不管显示什么内容,都需要这几个状态。在大型项目中,可以把这个模式抽取成自定义 Hook,比如
usePaginatedList。
状态模式的复用是 React 开发的重要技巧。识别出重复的状态模式,然后抽取成可复用的 Hook,可以大大减少代码重复。
数据加载函数
加载排行榜数据:
const loadData = async (pageNum: number, append = false) => {
try {
if (pageNum === 1) setLoading(true);
else setLoadingMore(true);
const res = await getTopAnime(pageNum, filter);
const newData = res.data || [];
API 调用:
getTopAnime(pageNum, filter)- 获取排行榜数据pageNum- 页码filter- 筛选条件
getTopAnime是封装好的 API 函数。Jikan API 的排行榜接口支持多种筛选条件,通过 filter 参数传递。API 会返回按评分排序的动漫列表。
注意这里的 filter 是从路由参数获取的,在整个组件生命周期内保持不变。这意味着用户不能在页面内切换筛选条件,需要返回上一页重新选择。这是一种简化的设计,适合入口明确的场景。
if (append) {
setAnimeList(prev => [...prev, ...newData]);
} else {
setAnimeList(newData);
}
setHasMore(res.pagination?.has_next_page || false);
} catch (error) {
console.error('Load error:', error);
} finally {
setLoading(false);
setLoadingMore(false);
}
};
数据处理逻辑:
- 追加模式合并数据,替换模式直接设置
- 从分页信息中获取是否还有下一页
- 错误时打印日志
- 最终重置加载状态
这段代码和季度动漫页完全一样,再次证明了分页加载的模式是固定的。唯一的区别是调用的 API 函数不同。
初始加载和依赖
useEffect(() => {
loadData(1);
}, [filter]);
依赖说明:
- 依赖
filter参数 - 当筛选条件变化时重新加载
虽然在当前实现中 filter 不会变化(来自路由参数),但把它放在依赖数组中是正确的做法。这样如果将来需要支持动态切换筛选条件,代码不需要修改。
加载更多处理
const handleLoadMore = useCallback(() => {
if (!loadingMore && hasMore) {
const nextPage = page + 1;
setPage(nextPage);
loadData(nextPage, true);
}
}, [loadingMore, hasMore, page]);
加载条件:
- 当前没有在加载中
- 还有更多数据
这个函数也和季度动漫页一样。
useCallback缓存函数引用,依赖项包括所有在函数内使用的状态。
列表项渲染
这是这个页面的特色部分:
const renderItem = ({ item, index }: { item: Anime; index: number }) => (
<AnimeListItem
anime={item}
rank={index + 1}
onPress={() => navigation.navigate('AnimeDetail', { animeId: item.mal_id })}
/>
);
渲染参数:
item- 当前动漫数据index- 当前索引(从 0 开始)
组件属性:
anime- 动漫数据对象rank- 排名,index + 1(因为索引从 0 开始,排名从 1 开始)onPress- 点击回调,跳转到详情页
注意这里用的是
AnimeListItem而不是AnimeCard。两者的区别:
AnimeCard- 卡片样式,适合网格布局,显示封面图和基本信息AnimeListItem- 列表项样式,适合纵向列表,显示更多详细信息
rank={index + 1}是一个简单但重要的细节。用户看到的排名应该从 1 开始,而数组索引从 0 开始,所以需要加 1。
但这里有一个潜在问题:当加载第二页时,index 会从 0 重新开始,导致排名不正确。正确的做法应该是
rank={(page - 1) * pageSize + index + 1}。不过在当前实现中,因为数据是追加的,FlatList 会保持正确的索引。
加载状态渲染
if (loading) {
return (
<View style={styles.container}>
<Header title={title} showBack onBack={() => navigation.goBack()} />
<Loading fullScreen text="加载中..." />
</View>
);
}
设计要点:
- 加载时显示 Header,用户知道自己在哪
- 显示返回按钮,用户可以取消
- 全屏 Loading 提示正在加载
这个模式在所有分页列表页面中都是一样的。Header 始终显示,Loading 占据内容区域。
主页面结构
return (
<View style={styles.container}>
<Header title={title} showBack onBack={() => navigation.goBack()} />
<FlatList
data={animeList}
renderItem={renderItem}
keyExtractor={item => item.mal_id.toString()}
contentContainerStyle={styles.list}
showsVerticalScrollIndicator={false}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={loadingMore ? <Loading text="加载更多..." /> : null}
ListEmptyComponent={<EmptyState icon="movie" title="暂无数据" />}
/>
</View>
);
与季度动漫页的区别:
- 没有
numColumns={2},使用默认的单列布局 - 使用
AnimeListItem而不是AnimeCard - 标题来自路由参数
单列布局更适合排行榜。原因有几个:
- 排名序号需要明显显示,单列布局有更多空间
- 用户浏览排行榜时通常是从上到下,单列更符合阅读习惯
- 列表项可以显示更多信息(评分、集数、状态等)
样式定义
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.background,
},
list: {
padding: Spacing.md,
},
});
样式说明:
- 容器占满屏幕,使用主题背景色
- 列表有中等内边距
样式非常简洁,因为大部分样式都在
AnimeListItem组件内部定义。这是组件化的好处:页面只需要关心布局,具体的样式由组件自己负责。
AnimeListItem 组件
虽然组件代码不在这个文件中,但理解它的设计很重要:
<AnimeListItem
anime={item}
rank={index + 1}
onPress={() => navigation.navigate('AnimeDetail', { animeId: item.mal_id })}
/>
组件职责:
- 显示动漫封面图(小尺寸)
- 显示排名序号(如果传入 rank)
- 显示动漫标题
- 显示评分、集数、状态等信息
- 处理点击事件
AnimeListItem是一个可复用的组件,不仅用于排行榜,还可以用于搜索结果、收藏列表等场景。rank属性是可选的,只有排行榜页面才传入。
组件设计的原则是"高内聚、低耦合"。组件内部处理自己的样式和逻辑,外部只需要传入数据和回调。这样组件可以在不同页面复用,修改组件时也不会影响其他地方。
页面复用设计
这个页面通过路由参数实现了复用:
// 综合排行
navigation.navigate('TopAnime', { title: '热门排行' });
// 正在播出排行
navigation.navigate('TopAnime', { filter: 'airing', title: '正在热播' });
// 即将上映排行
navigation.navigate('TopAnime', { filter: 'upcoming', title: '即将上映' });
// 人气排行
navigation.navigate('TopAnime', { filter: 'bypopularity', title: '人气排行' });
// 最受喜爱排行
navigation.navigate('TopAnime', { filter: 'favorite', title: '最受喜爱' });
复用优势:
- 一个页面组件,多种用途
- 减少代码重复
- 统一的用户体验
- 维护成本低
这种设计模式叫"参数化页面"。页面的行为由参数决定,而不是硬编码。这样可以用一个组件满足多个相似的需求。
在 AnimeHub 应用中,首页的多个入口(正在热播、即将上映、人气排行、最受喜爱)都跳转到这个页面,只是传入不同的参数。
排行榜数据的特点
Jikan API 的排行榜数据有一些特点:
- 按评分排序 - 默认按 MAL 评分从高到低
- 数据量大 - 总共有数万部动漫
- 更新频率 - 评分会随时变化,排名也会变
- 筛选支持 - 可以按类型、状态等筛选
MAL(MyAnimeList)是全球最大的动漫数据库之一,用户可以给动漫评分(1-10分)。排行榜就是按这个评分排序的。
排行榜的前几名通常是经典作品,如《钢之炼金术师》《进击的巨人》等。这些作品评分高、评价人数多,排名相对稳定。
与其他排行页面的关系
应用中有多个排行相关的页面:
- 热门排行(TopAnimeScreen) - 按评分排序,本页面
- 人气排行(PopularAnimeScreen) - 按人气排序
- 最受喜爱(FavoriteAnimeScreen) - 按收藏数排序
这些页面的代码结构几乎一样,区别只在于 API 参数。在实际项目中,可以考虑合并成一个页面,通过参数区分。但为了教程的清晰性,我们保留了独立的页面文件。
性能考虑
排行榜页面的性能优化点:
- 分页加载 - 不一次性加载所有数据
- useCallback - 缓存回调函数,避免不必要的重渲染
- keyExtractor - 使用稳定的 key,帮助 FlatList 优化渲染
- 组件复用 - FlatList 会复用列表项组件
FlatList 是 React Native 中性能最好的列表组件。它只渲染可见区域的内容,滚动时复用已有的组件实例。对于长列表(如排行榜),这种优化至关重要。
如果用 ScrollView + map 渲染列表,所有项目都会一次性渲染,可能导致卡顿甚至崩溃。FlatList 的虚拟化机制解决了这个问题。
小结
热门排行页是一个标准的分页列表页面,使用 AnimeListItem 组件显示带排名的动漫列表。页面通过路由参数实现复用,可以显示不同筛选条件的排行榜。
列表布局比网格布局更适合排行榜场景,因为可以显示排名序号和更多详细信息。AnimeListItem 组件封装了列表项的样式和逻辑,页面只需要关心数据加载和布局。
分页加载的实现和季度动漫页一样,是一个可复用的模式。在大型项目中,可以把这个模式抽取成自定义 Hook,进一步减少代码重复。
下一篇会讲随机推荐页面,展示随机获取的动漫作品。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)