在这里插入图片描述

案例开源地址:https://atomgit.com/nutpi/Rn_openharmony_AnimeHub

这是 AnimeHub 项目实战系列的最后一篇。我们用最受喜爱页面来收尾,同时回顾整个项目的技术要点。

什么是"最受喜爱"

MyAnimeList 有个"Favorites"功能,用户可以把特别喜欢的动漫标记为"最爱"。这和普通的"加入列表"不同,Favorites 是精选中的精选,代表用户真正热爱的作品。

最受喜爱排行榜就是按 Favorites 数量排序的榜单。能上这个榜的作品,说明有大量用户把它标记为"最爱",是真正意义上的"神作"。

三个排行榜的区别

到这里,我们已经实现了三个排行榜:

排行榜 排序依据 反映的是
热门排行 评分 质量、口碑
人气排行 用户数 知名度、话题性
最受喜爱 收藏数 情感认同、死忠粉数量

一部作品可能评分高但收藏少(叫好不叫座),也可能收藏多但评分一般(有争议但有死忠粉)。三个榜单从不同角度展示动漫的价值。

代码实现

最受喜爱页面的代码和前两个排行榜几乎一样,只是 filter 参数不同:

const res = await getTopAnime(pageNum, 'favorite');

完整代码如下:

import React, { useEffect, useState, useCallback } from 'react';
import { View, FlatList, StyleSheet } from 'react-native';
import { Colors, Spacing } from '../../theme';
import { Anime } from '../../types';
import { getTopAnime } from '../../api/jikan';
import { AnimeListItem } from '../../components/anime';
import { Header, Loading, EmptyState } from '../../components/common';

export const FavoriteAnimeScreen = ({ navigation }: any) => {
  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);

  const loadData = async (pageNum: number, append = false) => {
    try {
      if (pageNum === 1) setLoading(true);
      else setLoadingMore(true);
      
      const res = await getTopAnime(pageNum, 'favorite');
      const newData = res.data || [];
      
      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);
    }
  };

  useEffect(() => {
    loadData(1);
  }, []);

  const handleLoadMore = useCallback(() => {
    if (!loadingMore && hasMore) {
      const nextPage = page + 1;
      setPage(nextPage);
      loadData(nextPage, true);
    }
  }, [loadingMore, hasMore, page]);

  const renderItem = ({ item, index }: { item: Anime; index: number }) => (
    <AnimeListItem
      anime={item}
      rank={index + 1}
      onPress={() => navigation.navigate('AnimeDetail', { animeId: item.mal_id })}
    />
  );

  if (loading) {
    return (
      <View style={styles.container}>
        <Header title="最受喜爱" showBack onBack={() => navigation.goBack()} />
        <Loading fullScreen text="加载中..." />
      </View>
    );
  }

  return (
    <View style={styles.container}>
      <Header 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="heart" title="暂无数据" />}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: Colors.background,
  },
  list: {
    padding: Spacing.md,
  },
});

空状态用了爱心图标(heart),和"最受喜爱"的主题呼应。

回顾:30 个页面用到的技术

写完 30 篇,我们来回顾一下整个项目用到的核心技术:

React Native 基础

  • 函数组件 - 所有页面都用函数组件,配合 Hooks 使用
  • JSX 语法 - 用类似 HTML 的语法描述 UI
  • StyleSheet - 用 JavaScript 对象定义样式
  • Flexbox 布局 - 几乎所有布局都基于 Flexbox

React Hooks

  • useState - 管理组件内部状态
  • useEffect - 处理副作用(数据加载、订阅等)
  • useCallback - 缓存函数引用,优化性能

核心组件

  • View - 容器组件,类似 div
  • Text - 文本组件
  • Image - 图片组件
  • ScrollView - 滚动容器
  • FlatList - 高性能列表,支持虚拟化
  • TouchableOpacity - 可点击的容器
  • TextInput - 输入框
  • Switch - 开关
  • Alert - 原生对话框

列表相关

  • FlatList 基础配置 - data、renderItem、keyExtractor
  • 分页加载 - onEndReached、onEndReachedThreshold
  • 网格布局 - numColumns
  • 列表装饰 - ListHeaderComponent、ListFooterComponent、ListEmptyComponent
  • 下拉刷新 - refreshing、onRefresh

导航

  • 页面跳转 - navigation.navigate()
  • 返回 - navigation.goBack()
  • 参数传递 - route.params
  • 嵌套导航 - Tab Navigator + Stack Navigator

状态管理

  • 本地状态 - useState 管理页面内状态
  • 全局状态 - Zustand 管理跨页面状态(收藏、历史)
  • 持久化 - AsyncStorage 保存数据到本地

API 调用

  • fetch - 发起网络请求
  • async/await - 处理异步操作
  • 错误处理 - try/catch/finally
  • API 封装 - 统一的请求函数

样式技巧

  • 主题系统 - Colors、Spacing、FontSize、BorderRadius
  • 条件样式 - [styles.base, condition && styles.active]
  • 动态样式 - 根据 props 或 state 计算样式

回顾:页面类型分类

30 个页面可以分为几类:

列表页面(最多)

  • 首页、搜索页、新番页、排行页
  • 各种分类列表、排行榜
  • 收藏页、历史记录

特点:FlatList + 分页加载 + 列表项组件

详情页面

  • 动漫详情、角色详情
  • 剧集列表、评论页、统计页

特点:ScrollView + 多区块布局 + 条件渲染

表单/设置页面

  • 设置页、关于页

特点:ScrollView + 分组 + 开关/跳转

特殊交互页面

  • 随机推荐(单条数据 + 刷新)
  • 放送时间表(标签切换)
  • 图片页(网格 + 大图预览)

回顾:设计模式

项目中反复出现的设计模式:

分页列表模式

const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);

几乎所有列表页面都用这个模式。

条件渲染模式

if (loading) return <Loading />;
if (error) return <Error />;
if (data.length === 0) return <Empty />;
return <Content />;

处理不同状态的标准方式。

组件复用模式

  • AnimeCard - 网格布局的动漫卡片
  • AnimeListItem - 列表布局的动漫项
  • Header - 统一的页面头部
  • Loading - 统一的加载状态
  • EmptyState - 统一的空状态

封装通用组件,在多个页面复用。

项目可以继续优化的方向

虽然 30 个页面已经完成,但项目还有很多可以优化的地方:

性能优化

  • 图片懒加载和缓存
  • 列表项组件用 React.memo 优化
  • 数据请求缓存(React Query)

用户体验

  • 骨架屏代替 Loading
  • 下拉刷新
  • 错误重试
  • 离线支持

代码质量

  • TypeScript 类型完善
  • 单元测试
  • E2E 测试
  • 代码规范检查

功能扩展

  • 用户登录
  • 评论功能
  • 分享功能
  • 多语言支持

写在最后

30 篇文章,30 个页面,覆盖了 React Native 开发的方方面面。从最基础的列表展示,到复杂的状态管理,从简单的页面跳转,到嵌套的导航结构。

希望这个系列能帮助你入门 React Native for OpenHarmony 开发。代码不是最重要的,重要的是理解背后的思路和模式。掌握了这些,你就能举一反三,开发出自己的应用。

动漫的世界很精彩,编程的世界也很精彩。愿你在两个世界都能找到乐趣。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐