在移动应用开发中,媒体类应用是一种常见的应用类型,需要考虑内容管理、用户交互、数据持久化等多个方面。本文将深入分析一个功能完备的 React Native 动漫应用实现,探讨其架构设计、状态管理、数据持久化以及跨端兼容性策略。

组件化

该实现采用了清晰的组件化架构,主要包含以下部分:

  • 主应用组件 (AnimeApp) - 负责整体布局和状态管理
  • 内容列表渲染 - 负责渲染动漫卡片列表
  • 标签页导航 - 实现历史记录和收藏的切换
  • 功能按钮 - 提供播放、收藏等功能

这种架构设计使得代码结构清晰,易于维护和扩展。主应用组件负责管理全局状态和业务逻辑,而各个功能部分负责具体的 UI 渲染,实现了关注点分离。

状态管理

AnimeApp 组件使用 useState 钩子管理多个关键状态:

const [animes, setAnimes] = useState<Anime[]>(initialAnimes);
const [history, setHistory] = useState<Anime[]>([]);
const [favorites, setFavorites] = useState<Anime[]>([]);
const [activeTab, setActiveTab] = useState<'history' | 'favorites'>('history');
const [loading, setLoading] = useState(true);

这种状态管理方式简洁高效,通过状态更新触发组件重新渲染,实现了动漫的展示、收藏和历史记录管理等功能。使用 TypeScript 类型定义确保了数据结构的类型安全,提高了代码的可靠性。


本地存储

应用集成了 AsyncStorage 实现数据持久化:

// 加载本地数据
useEffect(() => {
  loadData();
}, []);

const loadData = async () => {
  try {
    const storedHistory = await AsyncStorage.getItem(STORAGE_KEYS.WATCH_HISTORY);
    const storedFavorites = await AsyncStorage.getItem(STORAGE_KEYS.FAVORITES);

    if (storedHistory) {
      setHistory(JSON.parse(storedHistory));
    } else {
      setHistory(initialAnimes);
    }

    if (storedFavorites) {
      setFavorites(JSON.parse(storedFavorites));
    }
  } catch (error) {
    console.error('加载数据失败:', error);
    setHistory(initialAnimes);
  } finally {
    setLoading(false);
  }
};

// 保存数据到本地存储
const saveData = async (key: string, data: any) => {
  try {
    await AsyncStorage.setItem(key, JSON.stringify(data));
  } catch (error) {
    console.error('保存数据失败:', error);
  }
};

这种实现方式确保了应用数据在应用重启后不会丢失,提高了用户体验。通过 useEffect 钩子在组件挂载时加载数据,确保了应用启动时能够显示之前的状态。

数据同步

应用实现了数据同步机制,当用户进行收藏、播放等操作时,会同时更新内存中的状态和本地存储:

// 切换收藏状态
const toggleFavorite = (anime: Anime) => {
  const updatedFavorites = favorites.some(fav => fav.id === anime.id)
    ? favorites.filter(fav => fav.id !== anime.id)
    : [...favorites, anime];
  
  setFavorites(updatedFavorites);
  saveData(STORAGE_KEYS.FAVORITES, updatedFavorites);
  
  // 更新历史记录中的收藏状态
  const updatedHistory = history.map(item =>
    item.id === anime.id ? { ...item, isFavorite: !item.isFavorite } : item
  );
  setHistory(updatedHistory);
  saveData(STORAGE_KEYS.WATCH_HISTORY, updatedHistory);
};

这种实现方式确保了内存状态和本地存储的一致性,避免了数据不同步的问题。同时,当更新收藏状态时,也会同步更新历史记录中的对应项,确保了数据的一致性。


动漫管理功能

应用实现了完整的动漫管理功能:

  • 观看历史 - 记录用户观看过的动漫
  • 收藏管理 - 管理用户收藏的动漫
  • 标签页切换 - 在历史记录和收藏之间切换
  • 数据清空 - 提供清空历史记录和收藏的功能

这些功能覆盖了动漫应用的基本需求,为用户提供了便捷的动漫管理体验。


类型定义

该实现使用 TypeScript 定义了核心数据类型:

// 动漫类型
type Anime = {
  id: string;
  title: string;
  episode: string;
  cover: string;
  duration: string;
  watchedTime: string;
  date: string;
  isFavorite: boolean;
};

这个类型定义包含了动漫的完整信息,包括:

  • 基本信息 - 标题、集数、封面、时长
  • 播放状态 - 已观看时间、观看日期
  • 收藏状态 - 是否收藏

这种类型定义使得数据结构更加清晰,提高了代码的可读性和可维护性,同时也提供了类型安全保障。

数据组织

应用数据按照功能模块进行组织:

  • animes - 动漫列表
  • history - 观看历史记录
  • favorites - 收藏的动漫列表
  • activeTab - 当前激活的标签页
  • loading - 数据加载状态

这种数据组织方式使得数据管理更加清晰,易于扩展和维护。


在设计跨端动漫应用时,需要特别关注以下几个方面:

  1. 组件 API 兼容性 - 确保使用的 React Native 组件在鸿蒙系统上有对应实现
  2. 样式系统差异 - 不同平台对样式的支持程度不同,需要确保样式在两端都能正常显示
  3. 本地存储差异 - 不同平台的本地存储机制可能存在差异
  4. 图标系统 - 确保图标在不同平台上都能正常显示
  5. 媒体播放 - 不同平台的媒体播放 API 可能存在差异

针对上述问题,该实现采用了以下适配策略:

  1. 使用 React Native 核心组件 - 优先使用 React Native 内置的组件,如 View、Text、TouchableOpacity、ScrollView、FlatList 等
  2. 统一的样式定义 - 使用 StyleSheet.create 定义样式,确保样式在不同平台上的一致性
  3. Base64 图标 - 使用 Base64 编码的图标,确保图标在不同平台上的一致性
  4. 本地存储抽象 - 使用 AsyncStorage 进行本地存储,它在不同平台上都有对应实现
  5. 平台无关的媒体处理 - 暂时使用简化的媒体播放逻辑,避免依赖平台特定的媒体 API

当前实现使用 FlatList 渲染动漫列表,这是一个好的做法,但可以进一步优化:

// 优化前
<FlatList
  data={activeTab === 'history' ? history : favorites}
  renderItem={({ item }) => (
    <AnimeCard anime={item} />
  )}
  keyExtractor={item => item.id}
/>

// 优化后
<FlatList
  data={activeTab === 'history' ? history : favorites}
  renderItem={({ item }) => (
    <AnimeCard anime={item} />
  )}
  keyExtractor={item => item.id}
  initialNumToRender={5} // 初始渲染的项目数
  maxToRenderPerBatch={10} // 每批渲染的最大项目数
  windowSize={10} // 可见区域外渲染的项目数
  removeClippedSubviews={true} // 移除不可见的子视图
  updateCellsBatchingPeriod={100} // 单元格更新的批处理周期
  getItemLayout={(data, index) => ({
    length: ITEM_HEIGHT, // 预计算的项目高度
    offset: ITEM_HEIGHT * index,
    index
  })}
/>

2. 状态管理优化

当前实现使用多个 useState 钩子管理状态,可以考虑使用 useReducer 或状态管理库来管理复杂状态:

// 优化前
const [animes, setAnimes] = useState<Anime[]>(initialAnimes);
const [history, setHistory] = useState<Anime[]>([]);
const [favorites, setFavorites] = useState<Anime[]>([]);
const [activeTab, setActiveTab] = useState<'history' | 'favorites'>('history');
const [loading, setLoading] = useState(true);

// 优化后
type AppState = {
  animes: Anime[];
  history: Anime[];
  favorites: Anime[];
  activeTab: 'history' | 'favorites';
  loading: boolean;
};

type AppAction =
  | { type: 'SET_ANIMES'; payload: Anime[] }
  | { type: 'SET_HISTORY'; payload: Anime[] }
  | { type: 'SET_FAVORITES'; payload: Anime[] }
  | { type: 'SET_ACTIVE_TAB'; payload: 'history' | 'favorites' }
  | { type: 'SET_LOADING'; payload: boolean }
  | { type: 'ADD_TO_HISTORY'; payload: Anime }
  | { type: 'TOGGLE_FAVORITE'; payload: Anime }
  | { type: 'CLEAR_HISTORY' }
  | { type: 'CLEAR_FAVORITES' };

const initialState: AppState = {
  animes: initialAnimes,
  history: [],
  favorites: [],
  activeTab: 'history',
  loading: true
};

const appReducer = (state: AppState, action: AppAction): AppState => {
  switch (action.type) {
    case 'SET_ANIMES':
      return { ...state, animes: action.payload };
    case 'SET_HISTORY':
      return { ...state, history: action.payload };
    case 'SET_FAVORITES':
      return { ...state, favorites: action.payload };
    case 'SET_ACTIVE_TAB':
      return { ...state, activeTab: action.payload };
    case 'SET_LOADING':
      return { ...state, loading: action.payload };
    case 'ADD_TO_HISTORY':
      return {
        ...state,
        history: [action.payload, ...state.history.filter(item => item.id !== action.payload.id)]
      };
    case 'TOGGLE_FAVORITE':
      const updatedFavorites = state.favorites.some(fav => fav.id === action.payload.id)
        ? state.favorites.filter(fav => fav.id !== action.payload.id)
        : [...state.favorites, action.payload];
      
      const updatedHistory = state.history.map(item =>
        item.id === action.payload.id ? { ...item, isFavorite: !item.isFavorite } : item
      );
      
      return {
        ...state,
        favorites: updatedFavorites,
        history: updatedHistory
      };
    case 'CLEAR_HISTORY':
      return { ...state, history: [] };
    case 'CLEAR_FAVORITES':
      return {
        ...state,
        favorites: [],
        history: state.history.map(item => ({ ...item, isFavorite: false }))
      };
    default:
      return state;
  }
};

const [state, dispatch] = useReducer(appReducer, initialState);

3. 网络请求

当前实现使用静态数据,可以考虑集成网络请求获取实时动漫数据:

import axios from 'axios';

const fetchAnimes = async () => {
  try {
    setLoading(true);
    const response = await axios.get('https://api.example.com/animes');
    const animesData = response.data;
    setAnimes(animesData);
  } catch (error) {
    console.error('获取动漫数据失败:', error);
    // 使用本地存储的缓存数据
  } finally {
    setLoading(false);
  }
};

useEffect(() => {
  fetchAnimes();
}, []);

4. 导航系统

可以集成 React Navigation 实现动漫详情页面的导航:

import { createStackNavigator } from '@react-navigation/stack';

const Stack = createStackNavigator();

const App = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen 
          name="Home" 
          component={AnimeApp} 
          options={{ title: '动漫库' }} 
        />
        <Stack.Screen 
          name="AnimeDetail" 
          component={AnimeDetailScreen} 
          options={({ route }) => ({ title: route.params?.animeTitle || '动漫详情' })} 
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
};

const AnimeDetailScreen = ({ route }: { route: any }) => {
  const { animeId } = route.params;
  // 获取动漫详情并渲染
  return (
    <View style={styles.detailContainer}>
      {/* 动漫详情内容 */}
    </View>
  );
};

本文深入分析了一个功能完备的 React Native 动漫应用实现,从架构设计、状态管理、数据持久化到跨端兼容性都进行了详细探讨。该实现不仅功能完整,而且代码结构清晰,具有良好的可扩展性和可维护性。


理解这个功能完整的 React Native 动漫应用的技术实现细节,同时掌握其向鸿蒙(HarmonyOS)平台跨端适配的核心思路与实践方案。该应用具备本地数据持久化、收藏管理、观看历史记录、标签页切换等核心功能,是典型的内容类移动应用架构,涵盖了本地存储、列表渲染、状态管理等移动端开发的核心场景。

1. 数据模型

该动漫应用采用数据驱动+本地持久化的现代化 React Native 架构,核心数据模型和存储设计体现了内容类应用的典型特征:

// 动漫数据模型 - 覆盖内容类应用核心属性
type Anime = {
  id: string;               // 唯一标识
  title: string;            // 标题
  episode: string;          // 剧集信息
  cover: string;            // 封面图
  duration: string;         // 总时长
  watchedTime: string;      // 已观看时长
  date: string;             // 观看日期
  isFavorite: boolean;      // 收藏状态
};

// 本地存储键常量 - 统一管理存储键名
const STORAGE_KEYS = {
  WATCH_HISTORY: '@watch_history',
  FAVORITES: '@favorites',
};

数据模型设计亮点

  • 属性完整性:涵盖动漫播放类应用所需的核心属性,支持扩展;
  • 状态标识清晰isFavorite 布尔值明确标识收藏状态;
  • 存储键常量化:避免硬编码,便于维护和修改;
  • 类型安全:TypeScript 类型定义确保数据结构一致性。

2. 本地存储

应用基于 AsyncStorage 实现完整的本地数据持久化方案,这是 React Native 中轻量级数据存储的标准实践:

(1)数据加载
// 组件挂载时加载本地数据
useEffect(() => {
  loadData();
}, []);

const loadData = async () => {
  try {
    // 异步获取存储数据
    const storedHistory = await AsyncStorage.getItem(STORAGE_KEYS.WATCH_HISTORY);
    const storedFavorites = await AsyncStorage.getItem(STORAGE_KEYS.FAVORITES);

    // 数据存在则解析使用,否则使用初始数据
    if (storedHistory) {
      setHistory(JSON.parse(storedHistory));
    } else {
      setHistory(initialAnimes);
    }

    if (storedFavorites) {
      setFavorites(JSON.parse(storedFavorites));
    }
  } catch (error) {
    console.error('加载数据失败:', error);
    setHistory(initialAnimes); // 异常处理,保证应用可用性
  } finally {
    setLoading(false); // 无论成功失败,结束加载状态
  }
};
(2)通用保存方法
const saveData = async (key: string, data: any) => {
  try {
    await AsyncStorage.setItem(key, JSON.stringify(data));
  } catch (error) {
    console.error('保存数据失败:', error);
  }
};
(3)数据操作
// 添加到历史记录(去重 + 置顶)
const addToHistory = (anime: Anime) => {
  // 过滤掉已存在的同ID项,新项置顶
  const updatedHistory = [anime, ...history.filter(item => item.id !== anime.id)];
  setHistory(updatedHistory);
  saveData(STORAGE_KEYS.WATCH_HISTORY, updatedHistory);
};

// 切换收藏状态(双向同步)
const toggleFavorite = (anime: Anime) => {
  // 更新收藏列表
  const updatedFavorites = favorites.some(fav => fav.id === anime.id)
    ? favorites.filter(fav => fav.id !== anime.id) // 已收藏则移除
    : [...favorites, anime]; // 未收藏则添加
  
  setFavorites(updatedFavorites);
  saveData(STORAGE_KEYS.FAVORITES, updatedFavorites);
  
  // 同步更新历史记录中的收藏状态
  const updatedHistory = history.map(item => 
    item.id === anime.id ? { ...item, isFavorite: !item.isFavorite } : item
  );
  setHistory(updatedHistory);
  saveData(STORAGE_KEYS.WATCH_HISTORY, updatedHistory);
};

本地存储设计要点

  • 异步处理:所有存储操作使用 async/await 处理异步流程;
  • 异常处理:完善的 try/catch 保证应用稳定性;
  • 数据一致性:收藏状态在历史和收藏列表间双向同步;
  • 历史记录优化:自动去重并将最新观看内容置顶;
  • 安全清空:清空操作前增加确认弹窗,防止误操作。

(1)标签页切换系统

实现了典型的移动端标签页切换交互,状态驱动视图更新:

<View style={styles.tabContainer}>
  <TouchableOpacity 
    style={[styles.tab, activeTab === 'history' && styles.activeTab]}
    onPress={() => setActiveTab('history')}
  >
    <Text style={[styles.tabText, activeTab === 'history' && styles.activeTabText]}>观看历史</Text>
  </TouchableOpacity>
  <TouchableOpacity 
    style={[styles.tab, activeTab === 'favorites' && styles.activeTab]}
    onPress={() => setActiveTab('favorites')}
  >
    <Text style={[styles.tabText, activeTab === 'favorites' && styles.activeTabText]}>我的收藏</Text>
  </TouchableOpacity>
</View>
(2)列表渲染
{activeTab === 'history' ? (
  history.length > 0 ? (
    <FlatList
      data={history}
      renderItem={renderAnimeItem}
      keyExtractor={item => item.id}
      showsVerticalScrollIndicator={false}
    />
  ) : (
    <View style={styles.emptyState}>
      <Text style={styles.emptyStateText}>暂无观看历史</Text>
      <Text style={styles.emptyStateSubtext}>观看动漫后历史记录会显示在这里</Text>
    </View>
  )
) : (
  // 收藏列表类似实现
)}
(3)动漫项渲染
const renderAnimeItem = ({ item }: { item: Anime }) => (
  <View style={styles.animeItem}>
    {/* 封面区域 */}
    <TouchableOpacity style={styles.coverContainer} onPress={() => playAnime(item)}>
      <View style={styles.coverPlaceholder}>
        <Text style={styles.coverText}>📺</Text>
      </View>
      <Text style={styles.duration}>{item.duration}</Text>
    </TouchableOpacity>
    
    {/* 信息区域 */}
    <View style={styles.infoContainer}>
      {/* 标题和剧集 */}
      <View style={styles.titleContainer}>
        <Text style={styles.title} numberOfLines={1}>{item.title}</Text>
        <Text style={styles.episode}>{item.episode}</Text>
      </View>
      
      {/* 元数据 */}
      <View style={styles.metaContainer}>
        <Text style={styles.date}>{item.date}</Text>
        <Text style={styles.watchedTime}>已观看: {item.watchedTime}</Text>
      </View>
      
      {/* 操作按钮 */}
      <View style={styles.actionsContainer}>
        <TouchableOpacity 
          style={styles.actionButton} 
          onPress={() => playAnime(item)}
        >
          <Text style={styles.actionText}>▶️ 继续播放</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.favoriteButton} 
          onPress={() => toggleFavorite(item)}
        >
          <Text style={styles.favoriteText}>{item.isFavorite ? '❤️' : '🤍'}</Text>
        </TouchableOpacity>
      </View>
    </View>
  </View>
);

4. 样式系统设计

应用采用 StyleSheet.create 构建完整的样式体系,遵循 React Native 最佳实践:

  • 组件化样式:每个UI组件有独立的样式定义;
  • 状态关联样式:通过样式数组实现激活状态的样式切换;
  • 响应式尺寸:基于屏幕尺寸设计适配性布局;
  • 阴影效果:使用 elevationshadow 属性实现跨平台阴影;
  • 圆角设计:统一的圆角半径,符合现代UI设计规范。

将该 React Native 动漫应用适配到鸿蒙平台,核心是将 React 的状态管理、本地存储、列表渲染等核心能力映射到鸿蒙 ArkTS + ArkUI 生态,以下是完整的适配方案。

1. 核心技术

React Native 核心能力 鸿蒙 ArkTS 对应实现 适配关键说明
useState 状态管理 @State/@Link/@StorageProp 状态声明语法替换
useEffect 生命周期 aboutToAppear 组件挂载时加载数据
AsyncStorage 本地存储 Preferences 偏好设置 本地持久化方案替换
FlatList 列表渲染 List + LazyForEach 高性能列表渲染
TouchableOpacity Button + stateEffect(false) 可点击组件替换
StyleSheet.create @Styles/@Extend + 内联样式 样式体系重构
Alert.alert AlertDialog 组件 弹窗交互替换
条件渲染(三元运算符) if/else 条件渲染 视图控制语法适配
SafeAreaView .safeArea(true) 修饰符 安全区域适配

2. 鸿蒙端

// index.ets - 鸿蒙端动漫应用完整实现
import preferences from '@ohos.data.preferences';
import router from '@ohos.router';

// 定义存储键常量(与RN端保持一致)
const STORAGE_KEYS = {
  WATCH_HISTORY: '@watch_history',
  FAVORITES: '@favorites',
};

// 动漫数据类型定义
type Anime = {
  id: string;
  title: string;
  episode: string;
  cover: string;
  duration: string;
  watchedTime: string;
  date: string;
  isFavorite: boolean;
};

// 模拟初始数据
const initialAnimes: Anime[] = [
  {
    id: '1',
    title: '进击的巨人 最终季',
    episode: '第75话:道路',
    cover: '',
    duration: '24:05',
    watchedTime: '15:32',
    date: '2023-05-15',
    isFavorite: true
  },
  {
    id: '2',
    title: '鬼灭之刃 无限列车篇',
    episode: '第19话:炼狱',
    cover: '',
    duration: '23:48',
    watchedTime: '12:15',
    date: '2023-05-14',
    isFavorite: false
  },
  {
    id: '3',
    title: '我的英雄学院 第四季',
    episode: '第104话:英雄',
    cover: '',
    duration: '24:12',
    watchedTime: '20:45',
    date: '2023-05-13',
    isFavorite: true
  },
  {
    id: '4',
    title: '咒术回战',
    episode: '第45话:宿傩',
    cover: '',
    duration: '23:55',
    watchedTime: '8:20',
    date: '2023-05-12',
    isFavorite: false
  },
  {
    id: '5',
    title: '龙与虎',
    episode: '第12话:圣诞',
    cover: '',
    duration: '24:00',
    watchedTime: '18:30',
    date: '2023-05-11',
    isFavorite: true
  },
  {
    id: '6',
    title: '钢之炼金术师FA',
    episode: '第51话:真理',
    cover: '',
    duration: '24:30',
    watchedTime: '22:10',
    date: '2023-05-10',
    isFavorite: false
  },
];

@Entry
@Component
struct AnimeApp {
  // 核心状态管理(对应RN的useState)
  @State animes: Anime[] = initialAnimes;
  @State history: Anime[] = [];
  @State favorites: Anime[] = [];
  @State activeTab: 'history' | 'favorites' = 'history';
  @State loading: boolean = true;
  
  // 偏好设置实例
  private pref: preferences.Preferences | null = null;

  // 通用样式封装
  @Styles
  cardShadow() {
    .shadow({ radius: 2, color: '#000', opacity: 0.1, offsetX: 0, offsetY: 1 });
  }

  // 组件生命周期(对应RN的useEffect)
  async aboutToAppear() {
    await this.loadData();
  }

  // 初始化偏好设置
  private async getPreferences() {
    if (!this.pref) {
      this.pref = await preferences.getPreferences(getContext(), 'anime_app');
    }
    return this.pref;
  }

  // 加载本地数据(替换RN的AsyncStorage)
  private async loadData() {
    try {
      const pref = await this.getPreferences();
      
      // 从偏好设置读取数据
      const storedHistory = pref.get(STORAGE_KEYS.WATCH_HISTORY, '');
      const storedFavorites = pref.get(STORAGE_KEYS.FAVORITES, '');

      if (storedHistory) {
        this.history = JSON.parse(storedHistory);
      } else {
        this.history = initialAnimes;
      }

      if (storedFavorites) {
        this.favorites = JSON.parse(storedFavorites);
      }
    } catch (error) {
      console.error('加载数据失败:', error);
      this.history = initialAnimes;
    } finally {
      this.loading = false;
    }
  }

  // 保存数据到本地存储(替换RN的saveData)
  private async saveData(key: string, data: any) {
    try {
      const pref = await this.getPreferences();
      await pref.put(key, JSON.stringify(data));
      await pref.flush(); // 立即刷盘
    } catch (error) {
      console.error('保存数据失败:', error);
    }
  }

  // 添加到历史记录(逻辑与RN完全一致)
  private addToHistory(anime: Anime) {
    const updatedHistory = [anime, ...this.history.filter(item => item.id !== anime.id)];
    this.history = updatedHistory;
    this.saveData(STORAGE_KEYS.WATCH_HISTORY, updatedHistory);
  }

  // 切换收藏状态(逻辑与RN完全一致)
  private toggleFavorite(anime: Anime) {
    const updatedFavorites = this.favorites.some(fav => fav.id === anime.id)
      ? this.favorites.filter(fav => fav.id !== anime.id)
      : [...this.favorites, anime];
    
    this.favorites = updatedFavorites;
    this.saveData(STORAGE_KEYS.FAVORITES, updatedFavorites);
    
    // 同步更新历史记录中的收藏状态
    this.history = this.history.map(item => 
      item.id === anime.id ? { ...item, isFavorite: !item.isFavorite } : item
    );
    this.saveData(STORAGE_KEYS.WATCH_HISTORY, this.history);
  }

  // 清空历史记录(替换RN的Alert.alert)
  private clearHistory() {
    AlertDialog.show({
      title: '清空历史记录',
      message: '确定要清空所有观看历史吗?此操作无法撤销。',
      cancel: {
        value: '取消',
        action: () => {}
      },
      confirm: {
        value: '确定',
        action: () => {
          this.history = [];
          this.saveData(STORAGE_KEYS.WATCH_HISTORY, []);
        }
      }
    });
  }

  // 清空收藏(替换RN的Alert.alert)
  private clearFavorites() {
    AlertDialog.show({
      title: '清空收藏',
      message: '确定要清空所有收藏吗?此操作无法撤销。',
      cancel: {
        value: '取消',
        action: () => {}
      },
      confirm: {
        value: '确定',
        action: () => {
          this.favorites = [];
          this.saveData(STORAGE_KEYS.FAVORITES, []);
          
          // 更新历史记录中的收藏状态
          this.history = this.history.map(item => ({ ...item, isFavorite: false }));
          this.saveData(STORAGE_KEYS.WATCH_HISTORY, this.history);
        }
      }
    });
  }

  // 播放动画(替换RN的Alert.alert)
  private playAnime(anime: Anime) {
    this.addToHistory(anime);
    AlertDialog.show({
      title: '播放',
      message: `正在播放: ${anime.title} - ${anime.episode}`,
      confirm: { value: '确定' }
    });
  }

  // 渲染动漫项(Builder函数替换RN的renderAnimeItem)
  @Builder
  renderAnimeItem(item: Anime) {
    Row({ space: 12 }) {
      // 封面区域
      Button()
        .width(100)
        .height(60)
        .backgroundColor('#e2e8f0')
        .borderRadius(8)
        .stateEffect(true)
        .onClick(() => this.playAnime(item))
        .contentLayout(ContentLayout.Center)
        .overlay(
          Text(item.duration)
            .fontSize(10)
            .fontColor('#ffffff')
            .backgroundColor('rgba(0,0,0,0.7)')
            .padding({ left: 4, right: 4, top: 2, bottom: 2 })
            .borderRadius(4)
            .position({ bottom: 4, right: 4 })
        ) {
          Text('📺')
            .fontSize(24);
        }
      
      // 信息区域
      Column({ space: 8 }) {
        // 标题和剧集
        Column({ space: 4 }) {
          Text(item.title)
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#1e293b')
            .maxLines(1);
          
          Text(item.episode)
            .fontSize(14)
            .fontColor('#64748b');
        }
        
        // 元数据
        Row({ space: 0 }) {
          Text(item.date)
            .fontSize(12)
            .fontColor('#94a3b8');
          
          Text(`已观看: ${item.watchedTime}`)
            .fontSize(12)
            .fontColor('#94a3b8')
            .marginLeft('auto');
        }
        
        // 操作按钮
        Row({ space: 0 }) {
          Button('▶️ 继续播放')
            .backgroundColor('#3b82f6')
            .padding({ left: 12, right: 12, top: 6, bottom: 6 })
            .borderRadius(6)
            .fontSize(12)
            .fontColor('#ffffff')
            .fontWeight(FontWeight.Medium)
            .stateEffect(true)
            .onClick(() => this.playAnime(item));
          
          Button(item.isFavorite ? '❤️' : '🤍')
            .backgroundColor(Color.Transparent)
            .padding(6)
            .fontSize(18)
            .stateEffect(true)
            .marginLeft('auto')
            .onClick(() => this.toggleFavorite(item));
        }
      }
      .flex(1);
    }
    .backgroundColor('#ffffff')
    .borderRadius(12)
    .padding(12)
    .marginBottom(12)
    .cardShadow();
  }

  // 空状态渲染
  @Builder
  renderEmptyState(title: string, subtext: string) {
    Column({ space: 8 }) {
      Text(title)
        .fontSize(16)
        .fontColor('#64748b');
      
      Text(subtext)
        .fontSize(14)
        .fontColor('#94a3b8')
        .textAlign(TextAlign.Center);
    }
    .width('100%')
    .paddingVertical(60)
    .alignItems(ItemAlign.Center);
  }

  build() {
    Column({ space: 0 }) {
      // 加载状态
      if (this.loading) {
        Column() {
          Text('加载中...')
            .fontSize(18)
            .fontColor('#64748b');
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
        .alignItems(ItemAlign.Center);
        return;
      }

      // 头部组件
      this.Header();
      
      // 内容区域
      Scroll() {
        Column({ space: 16 }) {
          // 标签页切换
          this.TabContainer();

          // 功能按钮
          this.FunctionButtons();

          // 内容列表
          if (this.activeTab === 'history') {
            if (this.history.length > 0) {
              List() {
                LazyForEach(new AnimeDataSource(this.history), (item: Anime) => {
                  ListItem() {
                    this.renderAnimeItem(item);
                  }
                });
              }
              .width('100%')
              .scrollBar(BarState.Off);
            } else {
              this.renderEmptyState('暂无观看历史', '观看动漫后历史记录会显示在这里');
            }
          } else {
            if (this.favorites.length > 0) {
              List() {
                LazyForEach(new AnimeDataSource(this.favorites), (item: Anime) => {
                  ListItem() {
                    this.renderAnimeItem(item);
                  }
                });
              }
              .width('100%')
              .scrollBar(BarState.Off);
            } else {
              this.renderEmptyState('暂无收藏', '点击心形图标可收藏喜欢的动漫');
            }
          }

          // 使用说明
          this.InfoCard();
        }
        .padding(16)
        .width('100%');
      }
      .flex(1)
      .width('100%');

      // 底部导航
      this.BottomNav();
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f8fafc')
    .safeArea(true);
  }

  // 头部组件
  @Builder
  Header() {
    Row({ space: 0 }) {
      Text('微动漫')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1e293b');
      
      Row({ space: 12 }) {
        // 搜索按钮
        Button()
          .width(36)
          .height(36)
          .borderRadius(18)
          .backgroundColor('#f1f5f9')
          .stateEffect(true)
          .onClick(() => {
            AlertDialog.show({
              title: '搜索',
              confirm: { value: '确定' }
            });
          }) {
            Text('🔍')
              .fontSize(18)
              .fontColor('#64748b');
          }
        
        // 用户按钮
        Button()
          .width(36)
          .height(36)
          .borderRadius(18)
          .backgroundColor('#f1f5f9')
          .stateEffect(true)
          .onClick(() => {
            AlertDialog.show({
              title: '用户中心',
              confirm: { value: '确定' }
            });
          }) {
            Text('👤')
              .fontSize(18)
              .fontColor('#64748b');
          }
      }
      .marginLeft('auto');
    }
    .padding(20)
    .backgroundColor('#ffffff')
    .borderBottom({ width: 1, color: '#e2e8f0' })
    .width('100%');
  }

  // 标签页容器
  @Builder
  TabContainer() {
    Row({ space: 0 }) {
      // 历史标签
      Button('观看历史')
        .flex(1)
        .height(40)
        .backgroundColor(this.activeTab === 'history' ? '#3b82f6' : '#ffffff')
        .fontColor(this.activeTab === 'history' ? '#ffffff' : '#64748b')
        .fontSize(14)
        .fontWeight(this.activeTab === 'history' ? FontWeight.Medium : FontWeight.Normal)
        .borderRadius(16)
        .stateEffect(true)
        .onClick(() => this.activeTab = 'history');
      
      // 收藏标签
      Button('我的收藏')
        .flex(1)
        .height(40)
        .backgroundColor(this.activeTab === 'favorites' ? '#3b82f6' : '#ffffff')
        .fontColor(this.activeTab === 'favorites' ? '#ffffff' : '#64748b')
        .fontSize(14)
        .fontWeight(this.activeTab === 'favorites' ? FontWeight.Medium : FontWeight.Normal)
        .borderRadius(16)
        .stateEffect(true)
        .onClick(() => this.activeTab = 'favorites');
    }
    .backgroundColor('#ffffff')
    .borderRadius(20)
    .padding(4)
    .width('100%');
  }

  // 功能按钮
  @Builder
  FunctionButtons() {
    Button(this.activeTab === 'history' ? '🗑️ 清空历史' : '🗑️ 清空收藏')
      .width('100%')
      .backgroundColor('#f1f5f9')
      .padding(12)
      .borderRadius(8)
      .fontSize(14)
      .fontColor('#3b82f6')
      .fontWeight(FontWeight.Medium)
      .stateEffect(true)
      .onClick(() => {
        if (this.activeTab === 'history') {
          this.clearHistory();
        } else {
          this.clearFavorites();
        }
      });
  }

  // 使用说明卡片
  @Builder
  InfoCard() {
    Column({ space: 8 }) {
      Text('使用说明')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1e293b');
      
      Text('• 自动记录观看历史')
        .fontSize(14)
        .fontColor('#64748b')
        .lineHeight(22);
      
      Text('• 点击心形图标收藏动漫')
        .fontSize(14)
        .fontColor('#64748b')
        .lineHeight(22);
      
      Text('• 可随时清除历史记录或收藏')
        .fontSize(14)
        .fontColor('#64748b')
        .lineHeight(22);
      
      Text('• 所有数据本地存储,保护隐私')
        .fontSize(14)
        .fontColor('#64748b')
        .lineHeight(22);
    }
    .backgroundColor('#ffffff')
    .borderRadius(12)
    .padding(16)
    .cardShadow()
    .width('100%');
  }

  // 底部导航
  @Builder
  BottomNav() {
    Row({ space: 0 }) {
      // 首页
      Button()
        .flex(1)
        .backgroundColor(Color.Transparent)
        .stateEffect(true)
        .onClick(() => {
          AlertDialog.show({
            title: '首页',
            confirm: { value: '确定' }
          });
        }) {
          Column({ space: 4 }) {
            Text('🏠')
              .fontSize(20)
              .fontColor('#94a3b8');
            
            Text('首页')
              .fontSize(12)
              .fontColor('#94a3b8');
          }
        };
      
      // 分类
      Button()
        .flex(1)
        .backgroundColor(Color.Transparent)
        .stateEffect(true)
        .onClick(() => {
          AlertDialog.show({
            title: '分类',
            confirm: { value: '确定' }
          });
        }) {
          Column({ space: 4 }) {
            Text('📚')
              .fontSize(20)
              .fontColor('#94a3b8');
            
            Text('分类')
              .fontSize(12)
              .fontColor('#94a3b8');
          }
        };
      
      // 历史
      Button()
        .flex(1)
        .backgroundColor(Color.Transparent)
        .stateEffect(true)
        .onClick(() => this.activeTab = 'history') {
          Column({ space: 4 }) {
            Text('🕒')
              .fontSize(20)
              .fontColor(this.activeTab === 'history' ? '#3b82f6' : '#94a3b8');
            
            Text('历史')
              .fontSize(12)
              .fontColor(this.activeTab === 'history' ? '#3b82f6' : '#94a3b8');
          }
        };
      
      // 收藏
      Button()
        .flex(1)
        .backgroundColor(Color.Transparent)
        .stateEffect(true)
        .onClick(() => this.activeTab = 'favorites') {
          Column({ space: 4 }) {
            Text('❤️')
              .fontSize(20)
              .fontColor(this.activeTab === 'favorites' ? '#3b82f6' : '#94a3b8');
            
            Text('收藏')
              .fontSize(12)
              .fontColor(this.activeTab === 'favorites' ? '#3b82f6' : '#94a3b8');
          }
        };
    }
    .backgroundColor('#ffffff')
    .borderTop({ width: 1, color: '#e2e8f0' })
    .paddingVertical(12)
    .width('100%');
  }
}

// 鸿蒙List数据源(LazyForEach必需)
class AnimeDataSource implements IDataSource {
  private data: Anime[];
  private listener: DataChangeListener | null = null;

  constructor(data: Anime[]) {
    this.data = data;
  }

  totalCount(): number {
    return this.data.length;
  }

  getData(index: number): Anime {
    return this.data[index];
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    this.listener = listener;
  }

  unregisterDataChangeListener(): void {
    this.listener = null;
  }
}

(1)本地存储

React Native 的 AsyncStorage 替换为鸿蒙 Preferences 偏好设置:

// React Native
const loadData = async () => {
  const storedHistory = await AsyncStorage.getItem(STORAGE_KEYS.WATCH_HISTORY);
  if (storedHistory) setHistory(JSON.parse(storedHistory));
};

// 鸿蒙
private async loadData() {
  const pref = await this.getPreferences();
  const storedHistory = pref.get(STORAGE_KEYS.WATCH_HISTORY, '');
  if (storedHistory) this.history = JSON.parse(storedHistory);
}

适配要点

  • 鸿蒙 Preferences 需要先获取实例,RN AsyncStorage 可直接使用;
  • 鸿蒙需要显式调用 flush() 刷盘,RN 自动持久化;
  • 数据读写API语法不同,但数据格式和处理逻辑完全一致。
(2)列表渲染

React Native FlatList 替换为鸿蒙 List + LazyForEach

// React Native
<FlatList
  data={history}
  renderItem={renderAnimeItem}
  keyExtractor={item => item.id}
/>

// 鸿蒙
List() {
  LazyForEach(new AnimeDataSource(this.history), (item: Anime) => {
    ListItem() {
      this.renderAnimeItem(item);
    }
  });
}

适配优势

  • 鸿蒙 LazyForEach 实现真正的懒加载,性能更优;
  • 内置列表复用机制,内存占用更低;
  • 支持更多列表特性(分组、索引、粘性头部等)。

React Native 组件体系到鸿蒙 ArkUI 的映射:

React Native 组件 鸿蒙 ArkUI 组件 适配说明
TouchableOpacity Button + stateEffect 可点击区域替换
View Column/Row/Stack 布局容器替换
Text Text API基本一致
SafeAreaView .safeArea(true) 修饰符方式实现
Alert.alert AlertDialog.show() 弹窗API替换
StyleSheet.create @Styles/内联样式 样式定义方式替换
(4)状态管理

React Hooks 到鸿蒙状态装饰器的映射:

// React Native
const [history, setHistory] = useState<Anime[]>([]);
const [activeTab, setActiveTab] = useState<'history' | 'favorites'>('history');

// 鸿蒙
@State history: Anime[] = [];
@State activeTab: 'history' | 'favorites' = 'history';

核心差异

  • React 状态更新需要调用 setter 函数,鸿蒙直接赋值即可;
  • React 状态更新是不可变的,鸿蒙直接修改状态对象;
  • 两者的响应式原理不同,但开发体验和效果一致。

真实演示案例代码:






// app.tsx
import React, { useState, useEffect } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions, Alert, FlatList, AsyncStorage } from 'react-native';

// Base64 图标库
const ICONS_BASE64 = {
  home: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  video: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  history: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  favorite: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  download: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  search: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  user: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  play: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};

const { width, height } = Dimensions.get('window');

// 动漫类型
type Anime = {
  id: string;
  title: string;
  episode: string;
  cover: string;
  duration: string;
  watchedTime: string;
  date: string;
  isFavorite: boolean;
};

// 本地存储键
const STORAGE_KEYS = {
  WATCH_HISTORY: '@watch_history',
  FAVORITES: '@favorites',
};

// 模拟数据
const initialAnimes: Anime[] = [
  {
    id: '1',
    title: '进击的巨人 最终季',
    episode: '第75话:道路',
    cover: '',
    duration: '24:05',
    watchedTime: '15:32',
    date: '2023-05-15',
    isFavorite: true
  },
  {
    id: '2',
    title: '鬼灭之刃 无限列车篇',
    episode: '第19话:炼狱',
    cover: '',
    duration: '23:48',
    watchedTime: '12:15',
    date: '2023-05-14',
    isFavorite: false
  },
  {
    id: '3',
    title: '我的英雄学院 第四季',
    episode: '第104话:英雄',
    cover: '',
    duration: '24:12',
    watchedTime: '20:45',
    date: '2023-05-13',
    isFavorite: true
  },
  {
    id: '4',
    title: '咒术回战',
    episode: '第45话:宿傩',
    cover: '',
    duration: '23:55',
    watchedTime: '8:20',
    date: '2023-05-12',
    isFavorite: false
  },
  {
    id: '5',
    title: '龙与虎',
    episode: '第12话:圣诞',
    cover: '',
    duration: '24:00',
    watchedTime: '18:30',
    date: '2023-05-11',
    isFavorite: true
  },
  {
    id: '6',
    title: '钢之炼金术师FA',
    episode: '第51话:真理',
    cover: '',
    duration: '24:30',
    watchedTime: '22:10',
    date: '2023-05-10',
    isFavorite: false
  },
];

const AnimeApp: React.FC = () => {
  const [animes, setAnimes] = useState<Anime[]>(initialAnimes);
  const [history, setHistory] = useState<Anime[]>([]);
  const [favorites, setFavorites] = useState<Anime[]>([]);
  const [activeTab, setActiveTab] = useState<'history' | 'favorites'>('history');
  const [loading, setLoading] = useState(true);

  // 加载本地数据
  useEffect(() => {
    loadData();
  }, []);

  const loadData = async () => {
    try {
      const storedHistory = await AsyncStorage.getItem(STORAGE_KEYS.WATCH_HISTORY);
      const storedFavorites = await AsyncStorage.getItem(STORAGE_KEYS.FAVORITES);

      if (storedHistory) {
        setHistory(JSON.parse(storedHistory));
      } else {
        setHistory(initialAnimes);
      }

      if (storedFavorites) {
        setFavorites(JSON.parse(storedFavorites));
      }
    } catch (error) {
      console.error('加载数据失败:', error);
      setHistory(initialAnimes);
    } finally {
      setLoading(false);
    }
  };

  // 保存数据到本地存储
  const saveData = async (key: string, data: any) => {
    try {
      await AsyncStorage.setItem(key, JSON.stringify(data));
    } catch (error) {
      console.error('保存数据失败:', error);
    }
  };

  // 添加到历史记录
  const addToHistory = (anime: Anime) => {
    const updatedHistory = [anime, ...history.filter(item => item.id !== anime.id)];
    setHistory(updatedHistory);
    saveData(STORAGE_KEYS.WATCH_HISTORY, updatedHistory);
  };

  // 切换收藏状态
  const toggleFavorite = (anime: Anime) => {
    const updatedFavorites = favorites.some(fav => fav.id === anime.id)
      ? favorites.filter(fav => fav.id !== anime.id)
      : [...favorites, anime];
    
    setFavorites(updatedFavorites);
    saveData(STORAGE_KEYS.FAVORITES, updatedFavorites);
    
    // 更新历史记录中的收藏状态
    const updatedHistory = history.map(item => 
      item.id === anime.id ? { ...item, isFavorite: !item.isFavorite } : item
    );
    setHistory(updatedHistory);
    saveData(STORAGE_KEYS.WATCH_HISTORY, updatedHistory);
  };

  // 清空历史记录
  const clearHistory = () => {
    Alert.alert(
      '清空历史记录',
      '确定要清空所有观看历史吗?此操作无法撤销。',
      [
        { text: '取消', style: 'cancel' },
        {
          text: '确定',
          onPress: () => {
            setHistory([]);
            saveData(STORAGE_KEYS.WATCH_HISTORY, []);
          }
        }
      ]
    );
  };

  // 清空收藏
  const clearFavorites = () => {
    Alert.alert(
      '清空收藏',
      '确定要清空所有收藏吗?此操作无法撤销。',
      [
        { text: '取消', style: 'cancel' },
        {
          text: '确定',
          onPress: () => {
            setFavorites([]);
            saveData(STORAGE_KEYS.FAVORITES, []);
            
            // 更新历史记录中的收藏状态
            const updatedHistory = history.map(item => ({ ...item, isFavorite: false }));
            setHistory(updatedHistory);
            saveData(STORAGE_KEYS.WATCH_HISTORY, updatedHistory);
          }
        }
      ]
    );
  };

  // 播放动画
  const playAnime = (anime: Anime) => {
    addToHistory(anime);
    Alert.alert('播放', `正在播放: ${anime.title} - ${anime.episode}`);
  };

  // 渲染动漫项
  const renderAnimeItem = ({ item }: { item: Anime }) => (
    <View style={styles.animeItem}>
      <TouchableOpacity style={styles.coverContainer} onPress={() => playAnime(item)}>
        <View style={styles.coverPlaceholder}>
          <Text style={styles.coverText}>📺</Text>
        </View>
        <Text style={styles.duration}>{item.duration}</Text>
      </TouchableOpacity>
      
      <View style={styles.infoContainer}>
        <View style={styles.titleContainer}>
          <Text style={styles.title} numberOfLines={1}>{item.title}</Text>
          <Text style={styles.episode}>{item.episode}</Text>
        </div>
        
        <View style={styles.metaContainer}>
          <Text style={styles.date}>{item.date}</Text>
          <Text style={styles.watchedTime}>已观看: {item.watchedTime}</Text>
        </div>
        
        <View style={styles.actionsContainer}>
          <TouchableOpacity 
            style={styles.actionButton} 
            onPress={() => playAnime(item)}
          >
            <Text style={styles.actionText}>▶️ 继续播放</Text>
          </TouchableOpacity>
          
          <TouchableOpacity 
            style={styles.favoriteButton} 
            onPress={() => toggleFavorite(item)}
          >
            <Text style={styles.favoriteText}>{item.isFavorite ? '❤️' : '🤍'}</Text>
          </TouchableOpacity>
        </div>
      </View>
    </View>
  );

  if (loading) {
    return (
      <SafeAreaView style={styles.container}>
        <View style={styles.loadingContainer}>
          <Text style={styles.loadingText}>加载中...</Text>
        </View>
      </SafeAreaView>
    );
  }

  return (
    <SafeAreaView style={styles.container}>
      {/* 头部 */}
      <View style={styles.header}>
        <Text style={styles.title}>微动漫</Text>
        <View style={styles.headerActions}>
          <TouchableOpacity style={styles.searchButton} onPress={() => Alert.alert('搜索')}>
            <Text style={styles.searchIcon}>🔍</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.userButton} onPress={() => Alert.alert('用户中心')}>
            <Text style={styles.userIcon}>👤</Text>
          </TouchableOpacity>
        </div>
      </View>

      <ScrollView style={styles.content}>
        {/* 标签页切换 */}
        <View style={styles.tabContainer}>
          <TouchableOpacity 
            style={[styles.tab, activeTab === 'history' && styles.activeTab]}
            onPress={() => setActiveTab('history')}
          >
            <Text style={[styles.tabText, activeTab === 'history' && styles.activeTabText]}>观看历史</Text>
          </TouchableOpacity>
          <TouchableOpacity 
            style={[styles.tab, activeTab === 'favorites' && styles.activeTab]}
            onPress={() => setActiveTab('favorites')}
          >
            <Text style={[styles.tabText, activeTab === 'favorites' && styles.activeTabText]}>我的收藏</Text>
          </TouchableOpacity>
        </View>

        {/* 功能按钮 */}
        <View style={styles.functionButtons}>
          <TouchableOpacity 
            style={styles.functionButton} 
            onPress={activeTab === 'history' ? clearHistory : clearFavorites}
          >
            <Text style={styles.functionButtonText}>
              {activeTab === 'history' ? '🗑️ 清空历史' : '🗑️ 清空收藏'}
            </Text>
          </TouchableOpacity>
        </View>

        {/* 内容列表 */}
        {activeTab === 'history' ? (
          history.length > 0 ? (
            <FlatList
              data={history}
              renderItem={renderAnimeItem}
              keyExtractor={item => item.id}
              showsVerticalScrollIndicator={false}
            />
          ) : (
            <View style={styles.emptyState}>
              <Text style={styles.emptyStateText}>暂无观看历史</Text>
              <Text style={styles.emptyStateSubtext}>观看动漫后历史记录会显示在这里</Text>
            </View>
          )
        ) : (
          favorites.length > 0 ? (
            <FlatList
              data={favorites}
              renderItem={renderAnimeItem}
              keyExtractor={item => item.id}
              showsVerticalScrollIndicator={false}
            />
          ) : (
            <View style={styles.emptyState}>
              <Text style={styles.emptyStateText}>暂无收藏</Text>
              <Text style={styles.emptyStateSubtext}>点击心形图标可收藏喜欢的动漫</Text>
            </View>
          )
        )}

        {/* 使用说明 */}
        <View style={styles.infoCard}>
          <Text style={styles.infoTitle}>使用说明</Text>
          <Text style={styles.infoText}>• 自动记录观看历史</Text>
          <Text style={styles.infoText}>• 点击心形图标收藏动漫</Text>
          <Text style={styles.infoText}>• 可随时清除历史记录或收藏</Text>
          <Text style={styles.infoText}>• 所有数据本地存储,保护隐私</Text>
        </View>
      </ScrollView>

      {/* 底部导航 */}
      <View style={styles.bottomNav}>
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => Alert.alert('首页')}
        >
          <Text style={styles.navIcon}>🏠</Text>
          <Text style={styles.navText}>首页</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => Alert.alert('分类')}
        >
          <Text style={styles.navIcon}>📚</Text>
          <Text style={styles.navText}>分类</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => setActiveTab('history')}
        >
          <Text style={styles.navIcon}>🕒</Text>
          <Text style={styles.navText}>历史</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => setActiveTab('favorites')}
        >
          <Text style={styles.navIcon}>❤️</Text>
          <Text style={styles.navText}>收藏</Text>
        </TouchableOpacity>
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f8fafc',
  },
  loadingContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  loadingText: {
    fontSize: 18,
    color: '#64748b',
  },
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    padding: 20,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#e2e8f0',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  headerActions: {
    flexDirection: 'row',
  },
  searchButton: {
    width: 36,
    height: 36,
    borderRadius: 18,
    backgroundColor: '#f1f5f9',
    alignItems: 'center',
    justifyContent: 'center',
    marginRight: 12,
  },
  searchIcon: {
    fontSize: 18,
    color: '#64748b',
  },
  userButton: {
    width: 36,
    height: 36,
    borderRadius: 18,
    backgroundColor: '#f1f5f9',
    alignItems: 'center',
    justifyContent: 'center',
  },
  userIcon: {
    fontSize: 18,
    color: '#64748b',
  },
  content: {
    flex: 1,
    padding: 16,
  },
  tabContainer: {
    flexDirection: 'row',
    backgroundColor: '#ffffff',
    borderRadius: 20,
    padding: 4,
    marginBottom: 16,
  },
  tab: {
    flex: 1,
    paddingVertical: 10,
    alignItems: 'center',
    borderRadius: 16,
  },
  activeTab: {
    backgroundColor: '#3b82f6',
  },
  tabText: {
    fontSize: 14,
    color: '#64748b',
  },
  activeTabText: {
    color: '#ffffff',
    fontWeight: '500',
  },
  functionButtons: {
    marginBottom: 16,
  },
  functionButton: {
    backgroundColor: '#f1f5f9',
    padding: 12,
    borderRadius: 8,
    alignItems: 'center',
  },
  functionButtonText: {
    fontSize: 14,
    color: '#3b82f6',
    fontWeight: '500',
  },
  animeItem: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    flexDirection: 'row',
    padding: 12,
    marginBottom: 12,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  coverContainer: {
    position: 'relative',
    marginRight: 12,
  },
  coverPlaceholder: {
    width: 100,
    height: 60,
    backgroundColor: '#e2e8f0',
    borderRadius: 8,
    alignItems: 'center',
    justifyContent: 'center',
  },
  coverText: {
    fontSize: 24,
  },
  duration: {
    position: 'absolute',
    bottom: 4,
    right: 4,
    backgroundColor: 'rgba(0,0,0,0.7)',
    color: '#ffffff',
    fontSize: 10,
    paddingHorizontal: 4,
    paddingVertical: 2,
    borderRadius: 4,
  },
  infoContainer: {
    flex: 1,
    justifyContent: 'space-between',
  },
  titleContainer: {
    marginBottom: 8,
  },
  title: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 4,
  },
  episode: {
    fontSize: 14,
    color: '#64748b',
  },
  metaContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginBottom: 8,
  },
  date: {
    fontSize: 12,
    color: '#94a3b8',
  },
  watchedTime: {
    fontSize: 12,
    color: '#94a3b8',
  },
  actionsContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  actionButton: {
    backgroundColor: '#3b82f6',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 6,
  },
  actionText: {
    color: '#ffffff',
    fontSize: 12,
    fontWeight: '500',
  },
  favoriteButton: {
    padding: 6,
  },
  favoriteText: {
    fontSize: 18,
  },
  emptyState: {
    alignItems: 'center',
    justifyContent: 'center',
    paddingVertical: 60,
  },
  emptyStateText: {
    fontSize: 16,
    color: '#64748b',
    marginBottom: 8,
  },
  emptyStateSubtext: {
    fontSize: 14,
    color: '#94a3b8',
    textAlign: 'center',
  },
  infoCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginTop: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  infoTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 12,
  },
  infoText: {
    fontSize: 14,
    color: '#64748b',
    lineHeight: 22,
    marginBottom: 8,
  },
  bottomNav: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    backgroundColor: '#ffffff',
    borderTopWidth: 1,
    borderTopColor: '#e2e8f0',
    paddingVertical: 12,
  },
  navItem: {
    alignItems: 'center',
    flex: 1,
  },
  navIcon: {
    fontSize: 20,
    color: '#94a3b8',
    marginBottom: 4,
  },
  activeNavIcon: {
    color: '#3b82f6',
  },
  navText: {
    fontSize: 12,
    color: '#94a3b8',
  },
  activeNavText: {
    color: '#3b82f6',
    fontWeight: '500',
  },
});

export default AnimeApp;


请添加图片描述


打包

接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

在这里插入图片描述

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:

在这里插入图片描述

最后运行效果图如下显示:

请添加图片描述
本文探讨了一个基于React Native的动漫应用实现方案,重点分析了其架构设计、状态管理和跨平台兼容性策略。应用采用组件化架构,分离全局状态与UI渲染,使用TypeScript确保类型安全。通过AsyncStorage实现数据持久化,并采用同步机制保证内存与本地存储的一致性。针对跨平台开发,提出了组件兼容性、样式统一、本地存储抽象等适配策略。同时给出了性能优化建议,包括FlatList渲染优化和状态管理改进方案。该实现覆盖了动漫应用的核心功能需求,为移动端媒体类应用开发提供了参考架构。

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐