React Native鸿蒙跨平台跨平台阅读应用实现方案,包含书籍展示、分类筛选、搜索排序等功能模块,通过清晰的状态管理实现数据筛选与排序

在移动应用开发中,阅读和书籍管理应用是一种常见的应用类型,需要考虑书籍展示、分类管理、搜索排序等多个方面。本文将深入分析一个功能完备的 React Native 阅读应用实现,探讨其架构设计、状态管理、数据处理以及跨端兼容性策略。

组件化

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

  • 主应用组件 (ReadingApp) - 负责整体布局和状态管理
  • 书籍列表渲染 - 负责渲染书籍卡片列表
  • 书架筛选 - 提供按书架筛选书籍的功能
  • 搜索功能 - 提供搜索书籍的功能
  • 排序功能 - 提供按标题、作者、评分或年份排序的功能

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

状态管理

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

typescript 复制代码
const [selectedShelf, setSelectedShelf] = useState<string>('1');
const [bookList, setBookList] = useState<Book[]>(books);
const [sortBy, setSortBy] = useState<'title' | 'author' | 'rating' | 'year'>('title');
const [searchQuery, setSearchQuery] = useState<string>('');

这种状态管理方式简洁高效,通过状态更新触发组件重新渲染,实现了书籍的筛选、搜索、排序等功能。使用 TypeScript 联合类型确保了 sortBy 的取值只能是预定义的几个选项之一,提高了代码的类型安全性。


书籍筛选与排序

应用实现了灵活的书籍筛选与排序功能:

typescript 复制代码
// 获取当前书架的书籍
const getCurrentShelfBooks = () => {
  const shelfName = shelves.find(shelf => shelf.id === selectedShelf)?.name;
  return bookList.filter(book =>
    book.shelf === shelfName &&
    (searchQuery === '' || 
     book.title.toLowerCase().includes(searchQuery.toLowerCase()) || 
     book.author.toLowerCase().includes(searchQuery.toLowerCase()))
  ).sort((a, b) => {
    switch (sortBy) {
      case 'title':
        return a.title.localeCompare(b.title);
      case 'author':
        return a.author.localeCompare(b.author);
      case 'rating':
        return b.rating - a.rating;
      case 'year':
        return b.publishYear - a.publishYear;
      default:
        return 0;
    }
  });
};

这种实现方式支持多重筛选和排序条件:

  • 书架筛选 - 只显示当前选中书架的书籍
  • 搜索筛选 - 根据标题或作者搜索书籍
  • 排序 - 按标题、作者、评分或年份排序

这种灵活的筛选和排序功能为用户提供了便捷的书籍管理体验。

书籍卡片

应用实现了书籍卡片的渲染:

typescript 复制代码
// 渲染书籍项
const renderBookItem = ({ item }: { item: Book }) => (
  <View style={styles.bookCard}>
    <View style={styles.coverContainer}>
      <View style={styles.coverPlaceholder}>
        <Text style={styles.coverText}>📖</Text>
      </View>
    </View>
    
    <View style={styles.infoContainer}>
      <View style={styles.headerContainer}>
        <Text style={styles.bookTitle}>{item.title}</Text>
        <Text style={styles.rating}>⭐ {item.rating}</Text>
      </View>
      
      {/* 更多书籍信息... */}
    </View>
  </View>
);

这种实现方式清晰展示了书籍的详细信息,包括标题、评分等,并提供了书籍封面的占位符。


类型定义

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

  1. Book - 书籍类型,包含书籍的完整信息,如 ID、标题、作者、类型、页数、评分、出版年份、描述、书架和封面
  2. Shelf - 书架类型,包含书架的 ID、名称、图标和书籍数量

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

数据组织

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

  • shelves - 书架列表
  • books - 书籍列表
  • bookList - 当前管理的书籍集合
  • selectedShelf - 当前选中的书架
  • sortBy - 排序方式
  • searchQuery - 搜索关键字

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


布局结构

应用界面采用了清晰的层次结构:

  • 顶部 - 显示应用标题和搜索栏
  • 书架筛选 - 显示书架列表,允许用户选择书架
  • 功能栏 - 显示排序、筛选等功能按钮
  • 书籍列表 - 显示当前筛选条件下的书籍列表

这种布局结构符合用户的使用习惯,用户可以快速了解应用内容并进行操作。

交互设计

应用实现了直观的交互设计:

  • 书架筛选 - 点击书架切换显示的书籍
  • 搜索 - 输入关键字搜索书籍
  • 排序 - 点击排序按钮切换排序方式
  • 书籍详情 - 点击书籍卡片查看详细信息

这些交互设计元素共同构成了良好的用户体验,使得书籍管理操作简单直观。

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

  1. 组件 API 兼容性 - 确保使用的 React Native 组件在鸿蒙系统上有对应实现
  2. 样式系统差异 - 不同平台对样式的支持程度不同,需要确保样式在两端都能正常显示
  3. 触摸事件处理 - 不同平台的触摸事件机制可能存在差异
  4. 图标系统 - 确保图标在不同平台上都能正常显示
  5. 性能优化 - 不同平台的性能特性不同,需要针对性优化

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

  1. 使用 React Native 核心组件 - 优先使用 React Native 内置的组件,如 View、Text、TouchableOpacity、ScrollView、FlatList 等
  2. 统一的样式定义 - 使用 StyleSheet.create 定义样式,确保样式在不同平台上的一致性
  3. Base64 图标 - 使用 Base64 编码的图标,确保图标在不同平台上的一致性
  4. 平台无关的图标 - 使用 Unicode 表情符号作为书架和书籍封面的图标,避免使用平台特定的图标库
  5. 简化的交互逻辑 - 使用简单直接的交互逻辑,减少平台差异带来的问题

渲染

  1. 列表优化 - 使用 FlatList 渲染书籍列表,提高长列表的渲染性能
  2. 条件渲染 - 只在需要时渲染特定的 UI 元素,减少不必要的渲染
  3. 组件拆分 - 将书籍卡片和功能按钮等拆分为独立的组件,提高渲染性能
  4. 样式复用 - 通过样式数组和条件样式,复用样式定义,减少样式计算开销

计算

  1. 减少重复计算 - 将筛选和排序逻辑封装为函数,避免在渲染过程中重复计算
  2. 优化排序算法 - 使用高效的排序算法,减少排序开销
  3. 延迟计算 - 只在需要时进行计算,避免不必要的计算

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

typescript 复制代码
// 优化前
<FlatList
  data={getCurrentShelfBooks()}
  renderItem={renderBookItem}
  keyExtractor={item => item.id}
/>

// 优化后
<FlatList
  data={getCurrentShelfBooks()}
  renderItem={renderBookItem}
  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 或状态管理库来管理复杂状态:

typescript 复制代码
// 优化前
const [selectedShelf, setSelectedShelf] = useState<string>('1');
const [bookList, setBookList] = useState<Book[]>(books);
const [sortBy, setSortBy] = useState<'title' | 'author' | 'rating' | 'year'>('title');
const [searchQuery, setSearchQuery] = useState<string>('');

// 优化后
type AppState = {
  selectedShelf: string;
  bookList: Book[];
  sortBy: 'title' | 'author' | 'rating' | 'year';
  searchQuery: string;
};

type AppAction =
  | { type: 'SET_SELECTED_SHELF'; payload: string }
  | { type: 'SET_BOOK_LIST'; payload: Book[] }
  | { type: 'SET_SORT_BY'; payload: 'title' | 'author' | 'rating' | 'year' }
  | { type: 'SET_SEARCH_QUERY'; payload: string }
  | { type: 'ADD_BOOK'; payload: Book }
  | { type: 'UPDATE_BOOK'; payload: Book }
  | { type: 'DELETE_BOOK'; payload: string };

const initialState: AppState = {
  selectedShelf: '1',
  bookList: books,
  sortBy: 'title',
  searchQuery: '',
};

const appReducer = (state: AppState, action: AppAction): AppState => {
  switch (action.type) {
    case 'SET_SELECTED_SHELF':
      return { ...state, selectedShelf: action.payload };
    case 'SET_BOOK_LIST':
      return { ...state, bookList: action.payload };
    case 'SET_SORT_BY':
      return { ...state, sortBy: action.payload };
    case 'SET_SEARCH_QUERY':
      return { ...state, searchQuery: action.payload };
    case 'ADD_BOOK':
      return { ...state, bookList: [...state.bookList, action.payload] };
    case 'UPDATE_BOOK':
      return {
        ...state,
        bookList: state.bookList.map(book =>
          book.id === action.payload.id ? action.payload : book
        )
      };
    case 'DELETE_BOOK':
      return {
        ...state,
        bookList: state.bookList.filter(book => book.id !== action.payload)
      };
    default:
      return state;
  }
};

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

3. 数据持久化

当前实现使用内存状态存储数据,可以考虑集成本地存储实现数据持久化:

typescript 复制代码
import AsyncStorage from '@react-native-async-storage/async-storage';

const STORAGE_KEYS = {
  BOOKS: '@books',
  SHELVES: '@shelves',
  SETTINGS: '@app_settings',
};

const ReadingApp = () => {
  const [bookList, setBookList] = useState<Book[]>(books);
  const [shelves, setShelves] = useState<Shelf[]>(shelves);
  const [settings, setSettings] = useState({
    selectedShelf: '1',
    sortBy: 'title' as 'title' | 'author' | 'rating' | 'year',
  });

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

  const loadData = async () => {
    try {
      const storedBooks = await AsyncStorage.getItem(STORAGE_KEYS.BOOKS);
      const storedShelves = await AsyncStorage.getItem(STORAGE_KEYS.SHELVES);
      const storedSettings = await AsyncStorage.getItem(STORAGE_KEYS.SETTINGS);

      if (storedBooks) {
        setBookList(JSON.parse(storedBooks));
      }

      if (storedShelves) {
        setShelves(JSON.parse(storedShelves));
      }

      if (storedSettings) {
        setSettings(JSON.parse(storedSettings));
      }
    } catch (error) {
      console.error('加载数据失败:', error);
    }
  };

  // 保存数据
  const saveData = async () => {
    try {
      await AsyncStorage.setItem(STORAGE_KEYS.BOOKS, JSON.stringify(bookList));
      await AsyncStorage.setItem(STORAGE_KEYS.SHELVES, JSON.stringify(shelves));
      await AsyncStorage.setItem(STORAGE_KEYS.SETTINGS, JSON.stringify(settings));
    } catch (error) {
      console.error('保存数据失败:', error);
    }
  };

  // 当数据变化时保存
  useEffect(() => {
    saveData();
  }, [bookList, shelves, settings]);

  // 其他代码...
};

4. 导航系统

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

typescript 复制代码
import { createStackNavigator } from '@react-navigation/stack';

const Stack = createStackNavigator();

const App = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen 
          name="Home" 
          component={ReadingApp} 
          options={{ title: '我的图书馆' }} 
        />
        <Stack.Screen 
          name="BookDetail" 
          component={BookDetailScreen} 
          options={({ route }) => ({ title: route.params?.bookTitle || '书籍详情' })} 
        />
        <Stack.Screen 
          name="AddBook" 
          component={AddBookScreen} 
          options={{ title: '添加书籍' }} 
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
};

const BookDetailScreen = ({ route }: { route: any }) => {
  const { bookId } = route.params;
  // 获取书籍详情并渲染
  return (
    <View style={styles.detailContainer}>
      {/* 书籍详情内容 */}
    </View>
  );
};

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


这个书架管理应用在 React Native 中的完整实现逻辑,并掌握其向鸿蒙(HarmonyOS)平台跨端适配的核心方案。该应用是典型的移动端数据驱动型列表应用,涵盖了多维度数据筛选排序、卡片式列表渲染、横向滚动选择、数据统计分析等核心场景,是跨端开发中极具代表性的实战案例。

1. 应用架构

该书架管理应用基于 TypeScript 构建了强类型的数据模型体系,为应用的稳定性和可维护性奠定了基础:

typescript 复制代码
// 书籍核心数据模型
type Book = {
  id: string;               // 唯一标识
  title: string;            // 书名
  author: string;           // 作者
  genre: string;            // 类型/流派
  pages: number;            // 页数
  rating: number;           // 评分
  publishYear: number;      // 出版年份
  description: string;      // 描述
  shelf: string;            // 所属书架
  cover: string;            // 封面图(预留)
};

// 书架分类模型
type Shelf = {
  id: string;               // 唯一标识
  name: string;             // 书架名称
  icon: string;             // 图标(emoji)
  bookCount: number;        // 书籍数量
};

数据设计亮点

  • 语义化字段设计:字段命名精准反映书籍属性,符合图书管理的业务逻辑;
  • 关联关系清晰 :通过 shelf 字段建立书籍与书架的多对一关联;
  • 预留扩展字段cover 字段为后续封面图功能预留扩展空间;
  • 模拟数据丰富:覆盖不同类型、评分、年份的书籍数据,满足测试需求;
  • 强类型约束:使用 TypeScript 接口避免运行时类型错误。

2. 状态管理

应用采用 React Hooks 构建了完整的状态管理体系,覆盖所有交互维度:

typescript 复制代码
const [selectedShelf, setSelectedShelf] = useState<string>('1'); // 当前选中书架
const [bookList, setBookList] = useState<Book[]>(books);         // 完整书籍列表
const [sortBy, setSortBy] = useState<'title' | 'author' | 'rating' | 'year'>('title'); // 排序维度
const [searchQuery, setSearchQuery] = useState<string>('');      // 搜索关键词

状态设计原则

  • 单一职责:每个状态仅管理一个维度的数据,职责边界清晰;
  • 类型安全:排序状态使用联合类型限制可选值,避免非法值;
  • 不可变更新:状态更新遵循 React 不可变原则,通过新数组替换实现;
  • 初始值合理:所有状态均设置合理初始值,避免 undefined 问题。

(1)多维度数据筛选

这是应用的核心业务逻辑,实现了书架筛选、关键词搜索、多维度排序的组合查询:

typescript 复制代码
const getCurrentShelfBooks = () => {
  const shelfName = shelves.find(shelf => shelf.id === selectedShelf)?.name;
  return bookList.filter(book => 
    // 书架筛选
    book.shelf === shelfName &&
    // 关键词搜索(书名/作者,不区分大小写)
    (searchQuery === '' || 
     book.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
     book.author.toLowerCase().includes(searchQuery.toLowerCase()))
  ).sort((a, b) => {
    // 多维度排序
    switch (sortBy) {
      case 'title':
        return a.title.localeCompare(b.title);      // 书名升序
      case 'author':
        return a.author.localeCompare(b.author);    // 作者升序
      case 'rating':
        return b.rating - a.rating;                 // 评分降序
      case 'year':
        return b.publishYear - a.publishYear;       // 出版年份降序
      default:
        return 0;
    }
  });
};

实现技巧

  • 链式操作:先过滤后排序,逻辑清晰且性能优化;
  • 多条件搜索:支持书名和作者两个维度的关键词匹配;
  • 本地化处理:转换为小写实现不区分大小写的搜索;
  • 排序策略丰富:支持字符串(书名/作者)和数值(评分/年份)两种排序类型;
  • 空值保护 :使用可选链操作符 ?. 避免空指针错误;
  • 降序排序:评分和年份采用降序,符合用户阅读习惯。
(2)高性能列表渲染

使用 FlatList 实现书籍列表的高性能渲染,自定义卡片式列表项:

typescript 复制代码
const renderBookItem = ({ item }: { item: Book }) => (
  <View style={styles.bookCard}>
    {/* 封面占位区 */}
    <View style={styles.coverContainer}>
      <View style={styles.coverPlaceholder}>
        <Text style={styles.coverText}>📖</Text>
      </View>
    </View>
    
    {/* 书籍信息区 */}
    <View style={styles.infoContainer}>
      {/* 头部(书名+评分) */}
      <View style={styles.headerContainer}>
        <Text style={styles.bookTitle}>{item.title}</Text>
        <Text style={styles.rating}>⭐ {item.rating}</Text>
      </View>
      
      {/* 详情信息 */}
      <Text style={styles.author}>作者: {item.author}</Text>
      <Text style={styles.genre}>类型: {item.genre}</Text>
      <Text style={styles.pages}>页数: {item.pages}页</Text>
      <Text style={styles.year}>出版: {item.publishYear}</Text>
      <Text style={styles.description} numberOfLines={2}>{item.description}</Text>
      
      {/* 操作按钮 */}
      <View style={styles.actionContainer}>
        <TouchableOpacity 
          style={styles.actionButton}
          onPress={() => Alert.alert('阅读', `开始阅读《${item.title}》`)}
        >
          <Text style={styles.actionButtonText}>阅读</Text>
        </TouchableOpacity>
        <TouchableOpacity 
          style={styles.actionButton}
          onPress={() => Alert.alert('编辑', `编辑《${item.title}》信息`)}
        >
          <Text style={styles.actionButtonText}>编辑</Text>
        </TouchableOpacity>
      </View>
    </View>
  </View>
);

UI设计亮点

  • 卡片式布局:使用圆角、阴影、留白构建现代卡片设计;
  • 图文排版:左侧封面占位 + 右侧信息区的经典图书列表布局;
  • 信息层级:通过字体大小、颜色区分不同重要程度的信息;
  • 评分可视化:使用 emoji 星星 + 数值展示评分,直观易懂;
  • 文本优化:描述文本限制行数,避免内容溢出;
  • 操作区设计:阅读/编辑按钮统一样式,交互引导清晰。
(3)数据操作

实现了书籍添加功能和多维度数据统计:

typescript 复制代码
// 添加新书
const addNewBook = () => {
  Alert.prompt(
    '添加新书',
    '请输入书名:',
    (value) => {
      if (value) {
        const newShelf = shelves.find(shelf => shelf.id === selectedShelf)?.name || '未知书架';
        const newBook: Book = {
          id: `${bookList.length + 1}`,
          title: value,
          author: '未知作者',
          genre: '其他',
          pages: 0,
          rating: 0,
          publishYear: new Date().getFullYear(),
          description: '新添加的书籍',
          shelf: newShelf,
          cover: ''
        };
        setBookList([newBook, ...bookList]); // 新书籍添加到列表顶部
      }
    }
  );
};

// 统计信息渲染
<View style={styles.statsContainer}>
  <View style={styles.statItem}>
    <Text style={styles.statNumber}>
      {currentShelfBooks.reduce((sum, book) => sum + book.pages, 0)}
    </Text>
    <Text style={styles.statLabel}>总页数</Text>
  </View>
  <View style={styles.statItem}>
    <Text style={styles.statNumber}>
      {currentShelfBooks.length > 0 
        ? (currentShelfBooks.reduce((sum, book) => sum + book.rating, 0) / currentShelfBooks.length).toFixed(1)
        : '0.0'}
    </Text>
    <Text style={styles.statLabel}>平均评分</Text>
  </View>
  <View style={styles.statItem}>
    <Text style={styles.statNumber}>
      {Math.max(...currentShelfBooks.map(book => book.publishYear), 0)}
    </Text>
    <Text style={styles.statLabel}>最新出版</Text>
  </View>
</View>

实现特点

  • 用户输入:使用 Alert.prompt 快速获取书名输入;
  • 关联书架:新添加书籍自动关联当前选中书架;
  • 默认值合理:设置合理的默认属性值,提升用户体验;
  • 数据统计:使用 reduce 实现总页数和平均评分计算;
  • 极值计算:使用 Math.max 结合扩展运算符获取最新出版年份;
  • 边界处理:平均评分计算时处理空列表情况,避免除以零错误;
  • 精度控制:平均评分保留1位小数,提升可读性。

(1)横向滚动书架选择器
tsx 复制代码
<ScrollView 
  horizontal 
  showsHorizontalScrollIndicator={false} 
  style={styles.shelvesContainer}
>
  <View style={styles.shelvesList}>
    {shelves.map(shelf => (
      <TouchableOpacity
        key={shelf.id}
        style={[
          styles.shelfItem,
          selectedShelf === shelf.id && styles.selectedShelf
        ]}
        onPress={() => setSelectedShelf(shelf.id)}
      >
        <Text style={styles.shelfIcon}>{shelf.icon}</Text>
        <Text style={styles.shelfName}>{shelf.name}</Text>
        <Text style={styles.shelfCount}>{shelf.bookCount}</Text>
      </TouchableOpacity>
    ))}
  </View>
</ScrollView>

实现要点

  • 横向滚动:horizontal 属性实现横向滚动布局;
  • 隐藏滚动条:showsHorizontalScrollIndicator={false} 优化视觉效果;
  • 选中状态:通过样式数组实现选中书架的视觉高亮;
  • 统一尺寸:固定宽度的书架卡片,保证布局一致性;
  • 信息层级:图标、名称、数量三层信息,层次清晰。
(2)多维度排序
tsx 复制代码
<View style={styles.sortContainer}>
  <Text style={styles.sortLabel}>排序方式:</Text>
  <View style={styles.sortOptions}>
    <TouchableOpacity 
      style={[styles.sortOption, sortBy === 'title' && styles.activeSortOption]}
      onPress={() => setSortBy('title')}
    >
      <Text style={styles.sortOptionText}>书名</Text>
    </TouchableOpacity>
    {/* 其他排序选项 */}
  </View>
</View>

交互设计

  • 容器化设计:排序选项包裹在卡片容器中,视觉独立;
  • 流式布局:flexWrap: 'wrap' 支持选项自动换行;
  • 状态反馈:选中选项通过背景色变化提供视觉反馈;
  • 标签引导:清晰的排序标签,提升可用性。

5. 样式

该应用的样式系统体现了移动端UI设计的最佳实践:

typescript 复制代码
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f8fafc', // 浅灰背景,提升阅读体验
  },
  bookCard: {
    backgroundColor: '#ffffff', // 白色卡片
    borderRadius: 12, // 大圆角设计
    flexDirection: 'row',
    padding: 12,
    marginBottom: 12,
    // 跨平台阴影
    elevation: 1, // Android阴影
    shadowColor: '#000', // iOS阴影
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  // 其他样式...
});

将该 React Native 书架管理应用适配到鸿蒙平台,核心是将 React 的状态管理、FlatList 列表、横向滚动、样式系统等核心能力映射到鸿蒙 ArkTS + ArkUI 生态,以下是完整的适配方案。

1. 核心技术栈映射

React Native 核心能力 鸿蒙 ArkTS 对应实现 适配关键说明
useState 状态管理 @State/@DerivedState 装饰器 基础状态+派生状态组合使用
FlatList 列表渲染 List + ForEach 组件 高性能列表渲染
横向 ScrollView Scroll + scrollDirection 滚动方向配置
TouchableOpacity Button + stateEffect(true) 可点击组件替换
StyleSheet.create @Styles/@Extend + 链式样式 样式体系重构
Alert.alert/prompt AlertDialog/TextInputDialog 弹窗交互替换
Array.filter/sort/reduce 数组方法完全复用 业务逻辑零修改
numberOfLines 行数限制 maxLines 属性 文本展示控制
flexWrap: 'wrap' flexWrap: FlexWrap.Wrap 流式布局适配

2. 鸿蒙端

tsx 复制代码
// index.ets - 鸿蒙端书架管理应用完整实现
import { BusinessError } from '@ohos.base';

// 类型定义(与RN端完全一致)
type Book = {
  id: string;
  title: string;
  author: string;
  genre: string;
  pages: number;
  rating: number;
  publishYear: number;
  description: string;
  shelf: string;
  cover: string;
};

type Shelf = {
  id: string;
  name: string;
  icon: string;
  bookCount: number;
};

// 模拟数据(与RN端完全一致)
const shelves: Shelf[] = [
  { id: '1', name: '已读书籍', icon: '📚', bookCount: 8 },
  { id: '2', name: '想读书籍', icon: '📖', bookCount: 5 },
  { id: '3', name: '正在阅读', icon: '👀', bookCount: 3 },
  { id: '4', name: '技术书籍', icon: '💻', bookCount: 6 },
  { id: '5', name: '小说文学', icon: '📝', bookCount: 7 },
  { id: '6', name: '个人收藏', icon: '❤️', bookCount: 4 },
];

const initialBooks: Book[] = [
  {
    id: '1',
    title: '百年孤独',
    author: '加西亚·马尔克斯',
    genre: '魔幻现实主义',
    pages: 360,
    rating: 9.2,
    publishYear: 1967,
    description: '一部描绘布恩迪亚家族七代人传奇故事的魔幻现实主义小说',
    shelf: '已读书籍',
    cover: ''
  },
  {
    id: '2',
    title: '1984',
    author: '乔治·奥威尔',
    genre: '反乌托邦',
    pages: 328,
    rating: 9.4,
    publishYear: 1949,
    description: '一个极权主义社会的寓言,探讨了自由与权力的关系',
    shelf: '已读书籍',
    cover: ''
  },
  {
    id: '3',
    title: '算法导论',
    author: '托马斯·科尔曼',
    genre: '计算机科学',
    pages: 1292,
    rating: 9.0,
    publishYear: 2009,
    description: '计算机算法的经典教材,涵盖广泛的数据结构和算法',
    shelf: '技术书籍',
    cover: ''
  },
  {
    id: '4',
    title: '三体',
    author: '刘慈欣',
    genre: '科幻小说',
    pages: 302,
    rating: 8.8,
    publishYear: 2006,
    description: '中国科幻文学的里程碑之作,讲述人类与三体文明的接触',
    shelf: '已读书籍',
    cover: ''
  },
  {
    id: '5',
    title: '深入理解计算机系统',
    author: '兰德尔·布莱恩特',
    genre: '计算机科学',
    pages: 784,
    rating: 9.5,
    publishYear: 2011,
    description: '从程序员视角了解计算机系统的权威指南',
    shelf: '技术书籍',
    cover: ''
  },
  {
    id: '6',
    title: '活着',
    author: '余华',
    genre: '现代文学',
    pages: 191,
    rating: 9.1,
    publishYear: 1993,
    description: '一个普通人在时代变迁中的苦难历程',
    shelf: '已读书籍',
    cover: ''
  },
  {
    id: '7',
    title: 'JavaScript高级程序设计',
    author: '马特·弗利',
    genre: '编程技术',
    pages: 852,
    rating: 8.7,
    publishYear: 2012,
    description: '前端开发必备经典教材',
    shelf: '技术书籍',
    cover: ''
  },
  {
    id: '8',
    title: '红楼梦',
    author: '曹雪芹',
    genre: '古典文学',
    pages: 1200,
    rating: 9.6,
    publishYear: 1791,
    description: '中国古典小说的巅峰之作',
    shelf: '想读书籍',
    cover: ''
  },
  {
    id: '9',
    title: 'Effective Java',
    author: '约书亚·布洛克',
    genre: '编程技术',
    pages: 416,
    rating: 9.3,
    publishYear: 2017,
    description: 'Java编程的最佳实践指南',
    shelf: '正在阅读',
    cover: ''
  },
  {
    id: '10',
    title: '围城',
    author: '钱钟书',
    genre: '现代文学',
    pages: 311,
    rating: 8.9,
    publishYear: 1947,
    description: '描绘知识分子生活的讽刺小说',
    shelf: '小说文学',
    cover: ''
  },
];

@Entry
@Component
struct ReadingApp {
  // 基础状态(对应RN的useState)
  @State selectedShelf: string = '1';
  @State bookList: Book[] = initialBooks;
  @State sortBy: 'title' | 'author' | 'rating' | 'year' = 'title';
  @State searchQuery: string = '';

  // 派生状态 - 计算当前书架的书籍列表
  @DerivedState
  get currentShelfBooks(): Book[] {
    const shelfName = shelves.find(shelf => shelf.id === this.selectedShelf)?.name;
    return this.bookList.filter(book => 
      book.shelf === shelfName &&
      (this.searchQuery === '' || 
       book.title.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
       book.author.toLowerCase().includes(this.searchQuery.toLowerCase()))
    ).sort((a, b) => {
      switch (this.sortBy) {
        case 'title':
          return a.title.localeCompare(b.title);
        case 'author':
          return a.author.localeCompare(b.author);
        case 'rating':
          return b.rating - a.rating;
        case 'year':
          return b.publishYear - a.publishYear;
        default:
          return 0;
      }
    });
  }

  // 派生状态 - 统计数据
  @DerivedState
  get statsData() {
    const books = this.currentShelfBooks;
    return {
      totalPages: books.reduce((sum, book) => sum + book.pages, 0),
      avgRating: books.length > 0 
        ? (books.reduce((sum, book) => sum + book.rating, 0) / books.length).toFixed(1)
        : '0.0',
      latestYear: books.length > 0 
        ? Math.max(...books.map(book => book.publishYear))
        : 0
    };
  }

  // 通用样式封装 - 卡片容器样式
  @Styles
  cardStyle() {
    .backgroundColor('#ffffff')
    .borderRadius(12)
    .shadow({ radius: 2, color: '#000', opacity: 0.1, offsetX: 0, offsetY: 1 });
  }

  // 通用样式封装 - 排序选项按钮样式
  @Styles
  sortOptionStyle(isActive: boolean) {
    .backgroundColor(isActive ? '#3b82f6' : '#f1f5f9')
    .paddingHorizontal(12)
    .paddingVertical(6)
    .borderRadius(20)
    .marginRight(8)
    .marginBottom(8);
  }

  // 添加新书
  private addNewBook() {
    // 鸿蒙的文本输入弹窗
    TextInputDialog.show({
      title: '添加新书',
      message: '请输入书名:',
      confirm: {
        value: '确定'
      },
      cancel: {
        value: '取消'
      },
      placeholder: '例如:百年孤独'
    }).then((result) => {
      if (result.result && result.value) {
        const newShelf = shelves.find(shelf => shelf.id === this.selectedShelf)?.name || '未知书架';
        const newBook: Book = {
          id: `${this.bookList.length + 1}`,
          title: result.value,
          author: '未知作者',
          genre: '其他',
          pages: 0,
          rating: 0,
          publishYear: new Date().getFullYear(),
          description: '新添加的书籍',
          shelf: newShelf,
          cover: ''
        };
        this.bookList = [newBook, ...this.bookList];
      }
    }).catch((err: BusinessError) => {
      console.error('添加书籍失败:', err);
    });
  }

  // 渲染书籍列表项
  @Builder
  renderBookItem(item: Book) {
    Row({ space: 12 }) {
      // 封面占位区
      Column() {
        Stack() {
          Column() {
            Text('📖')
              .fontSize(24);
          }
          .width(60)
          .height(80)
          .backgroundColor('#e2e8f0')
          .borderRadius(4)
          .alignItems(ItemAlign.Center)
          .justifyContent(FlexAlign.Center);
        }
      }

      // 书籍信息区
      Column({ space: 2, flexShrink: 1 }) {
        // 头部(书名+评分)
        Row({ space: 0 }) {
          Text(item.title)
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#1e293b')
            .flex(1);
          
          Text(`⭐ ${item.rating}`)
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#f59e0b');
        }
        .marginBottom(4);

        // 详情信息
        Text(`作者: ${item.author}`)
          .fontSize(12)
          .fontColor('#64748b');
        
        Text(`类型: ${item.genre}`)
          .fontSize(12)
          .fontColor('#64748b');
        
        Text(`页数: ${item.pages}页`)
          .fontSize(12)
          .fontColor('#94a3b8');
        
        Text(`出版: ${item.publishYear}`)
          .fontSize(12)
          .fontColor('#94a3b8')
          .marginBottom(4);
        
        Text(item.description)
          .fontSize(14)
          .fontColor('#64748b')
          .maxLines(2) // 对应RN的numberOfLines
          .marginBottom(8);

        // 操作按钮
        Row({ space: 8 }) {
          Button('阅读')
            .backgroundColor('#f1f5f9')
            .paddingHorizontal(12)
            .paddingVertical(6)
            .borderRadius(6)
            .fontSize(12)
            .fontColor('#3b82f6')
            .fontWeight(FontWeight.Medium)
            .stateEffect(true)
            .onClick(() => {
              AlertDialog.show({
                title: '阅读',
                message: `开始阅读《${item.title}》`,
                confirm: { value: '确定' }
              });
            });
          
          Button('编辑')
            .backgroundColor('#f1f5f9')
            .paddingHorizontal(12)
            .paddingVertical(6)
            .borderRadius(6)
            .fontSize(12)
            .fontColor('#3b82f6')
            .fontWeight(FontWeight.Medium)
            .stateEffect(true)
            .onClick(() => {
              AlertDialog.show({
                title: '编辑',
                message: `编辑《${item.title}》信息`,
                confirm: { value: '确定' }
              });
            });
        }
        .width('100%');
      }
    }
    .cardStyle()
    .padding(12)
    .marginBottom(12)
    .width('100%');
  }

  build() {
    Column({ space: 0 }) {
      // 头部导航栏
      this.Header();
      
      // 内容区域(滚动容器)
      Scroll() {
        Column({ space: 16 }) {
          // 搜索栏
          this.SearchBar();

          // 书架选择
          this.ShelfSelector();

          // 排序选项
          this.SortOptions();

          // 书架详情
          this.ShelfDetails();

          // 书籍列表标题
          Text(`${shelves.find(s => s.id === this.selectedShelf)?.name || '书架'} (${this.currentShelfBooks.length})`)
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor('#1e293b')
            .marginVertical(12)
            .width('100%');

          // 书籍列表(对应RN的FlatList)
          List({ space: 0 }) {
            ForEach(this.currentShelfBooks, (item) => {
              ListItem() {
                this.renderBookItem(item);
              }
            }, (item) => item.id);
          }
          .width('100%')
          .shrink(0)
          .edgeEffect(EdgeEffect.None)
          .scrollBar(BarState.Off);

          // 统计信息
          this.StatsSection();

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

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

  // 头部导航栏 - Builder函数封装
  @Builder
  Header() {
    Row({ space: 0 }) {
      Text('我的书架')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1e293b');
      
      // 添加按钮
      Button('+')
        .width(36)
        .height(36)
        .borderRadius(18)
        .backgroundColor('#3b82f6')
        .fontSize(20)
        .fontColor('#ffffff')
        .fontWeight(FontWeight.Bold)
        .stateEffect(true)
        .onClick(() => this.addNewBook())
        .marginLeft('auto');
    }
    .padding(20)
    .backgroundColor('#ffffff')
    .borderBottom({ width: 1, color: '#e2e8f0' })
    .width('100%');
  }

  // 搜索栏 - Builder函数封装
  @Builder
  SearchBar() {
    Row({ space: 12 }) {
      Text('🔍')
        .fontSize(18)
        .fontColor('#64748b');
      
      Text('搜索书名或作者')
        .fontSize(14)
        .fontColor('#94a3b8')
        .flex(1);
    }
    .cardStyle()
    .paddingVertical(12)
    .paddingHorizontal(16)
    .borderRadius(20)
    .width('100%');
  }

  // 书架选择器 - Builder函数封装
  @Builder
  ShelfSelector() {
    Column({ space: 0 }) {
      Text('选择书架')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1e293b')
        .marginVertical(12)
        .width('100%');
      
      // 横向滚动的书架列表
      Scroll({ scrollDirection: ScrollDirection.Horizontal }) {
        Row({ space: 12 }) {
          ForEach(shelves, (shelf) => {
            Button()
              .width(80)
              .height('auto')
              .backgroundColor(this.selectedShelf === shelf.id ? '#3b82f6' : '#ffffff')
              .borderRadius(12)
              .padding(12)
              .stateEffect(true)
              .onClick(() => this.selectedShelf = shelf.id)
              .shadow({ radius: 2, color: '#000', opacity: 0.1, offsetX: 0, offsetY: 1 }) {
                Column({ space: 4 }) {
                  Text(shelf.icon)
                    .fontSize(24);
                  
                  Text(shelf.name)
                    .fontSize(12)
                    .fontColor(this.selectedShelf === shelf.id ? '#ffffff' : '#64748b')
                    .fontWeight(FontWeight.Medium);
                  
                  Text(`${shelf.bookCount}`)
                    .fontSize(10)
                    .fontColor(this.selectedShelf === shelf.id ? '#ffffff' : '#94a3b8');
                }
                .alignItems(ItemAlign.Center);
              };
          }, (shelf) => shelf.id);
        }
        .width('auto');
      }
      .scrollBar(BarState.Off)
      .width('100%');
    }
  }

  // 排序选项 - Builder函数封装
  @Builder
  SortOptions() {
    Column({ space: 8 }) {
      Text('排序方式:')
        .fontSize(14)
        .fontColor('#64748b');
      
      Row({ space: 0 }) {
        // 书名排序
        Button('书名')
          .sortOptionStyle(this.sortBy === 'title')
          .fontSize(12)
          .fontColor(this.sortBy === 'title' ? '#ffffff' : '#3b82f6')
          .fontWeight(FontWeight.Medium)
          .stateEffect(true)
          .onClick(() => this.sortBy = 'title');
        
        // 作者排序
        Button('作者')
          .sortOptionStyle(this.sortBy === 'author')
          .fontSize(12)
          .fontColor(this.sortBy === 'author' ? '#ffffff' : '#3b82f6')
          .fontWeight(FontWeight.Medium)
          .stateEffect(true)
          .onClick(() => this.sortBy = 'author');
        
        // 评分排序
        Button('评分')
          .sortOptionStyle(this.sortBy === 'rating')
          .fontSize(12)
          .fontColor(this.sortBy === 'rating' ? '#ffffff' : '#3b82f6')
          .fontWeight(FontWeight.Medium)
          .stateEffect(true)
          .onClick(() => this.sortBy = 'rating');
        
        // 年份排序
        Button('年份')
          .sortOptionStyle(this.sortBy === 'year')
          .fontSize(12)
          .fontColor(this.sortBy === 'year' ? '#ffffff' : '#3b82f6')
          .fontWeight(FontWeight.Medium)
          .stateEffect(true)
          .onClick(() => this.sortBy = 'year');
      }
      .flexWrap(FlexWrap.Wrap) // 对应RN的flexWrap: 'wrap'
      .width('100%');
    }
    .cardStyle()
    .padding(16)
    .width('100%');
  }

  // 书架详情 - Builder函数封装
  @Builder
  ShelfDetails() {
    Row({ space: 0 }) {
      Text(`${shelves.find(s => s.id === this.selectedShelf)?.name || '书架'}详情`)
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1e293b');
      
      Text(`共 ${this.currentShelfBooks.length} 本书`)
        .fontSize(14)
        .fontColor('#64748b')
        .marginLeft('auto');
    }
    .cardStyle()
    .padding(16)
    .width('100%');
  }

  // 统计信息 - Builder函数封装
  @Builder
  StatsSection() {
    Row({ space: 0 }) {
      // 总页数
      Column({ space: 4 }) {
        Text(`${this.statsData.totalPages}`)
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3b82f6');
        
        Text('总页数')
          .fontSize(12)
          .fontColor('#64748b');
      }
      .flex(1)
      .alignItems(ItemAlign.Center);
      
      // 平均评分
      Column({ space: 4 }) {
        Text(`${this.statsData.avgRating}`)
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3b82f6');
        
        Text('平均评分')
          .fontSize(12)
          .fontColor('#64748b');
      }
      .flex(1)
      .alignItems(ItemAlign.Center);
      
      // 最新出版
      Column({ space: 4 }) {
        Text(`${this.statsData.latestYear}`)
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3b82f6');
        
        Text('最新出版')
          .fontSize(12)
          .fontColor('#64748b');
      }
      .flex(1)
      .alignItems(ItemAlign.Center);
    }
    .cardStyle()
    .padding(16)
    .width('100%');
  }

  // 使用说明 - Builder函数封装
  @Builder
  InfoSection() {
    Column({ space: 8 }) {
      Text('使用说明')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1e293b')
        .marginBottom(4);
      
      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);
    }
    .cardStyle()
    .padding(16)
    .width('100%');
  }

  // 底部导航 - Builder函数封装
  @Builder
  BottomNav() {
    Row({ space: 0 }) {
      // 首页
      Button()
        .flex(1)
        .backgroundColor(Color.Transparent)
        .stateEffect(true)
        .onClick(() => {
          AlertDialog.show({ title: '首页', message: '首页功能' });
        }) {
          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: '书架', message: '书架管理' });
        }) {
          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: '统计', message: '数据统计' });
        }) {
          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: '我的', message: '个人中心' });
        }) {
          Column({ space: 4 }) {
            Text('👤')
              .fontSize(20)
              .fontColor('#94a3b8');
            
            Text('我的')
              .fontSize(12)
              .fontColor('#94a3b8');
          }
        };
    }
    .backgroundColor('#ffffff')
    .borderTop({ width: 1, color: '#e2e8f0' })
    .paddingVertical(12)
    .width('100%');
  }
}

(1)状态管理

React Native 的多个 useState + 计算函数 升级为鸿蒙的 @State + @DerivedState 组合,特别优化了统计数据的管理:

tsx 复制代码
// React Native
const [selectedShelf, setSelectedShelf] = useState<string>('1');
const [bookList, setBookList] = useState<Book[]>(books);

// 计算函数
const getCurrentShelfBooks = () => {
  // 复杂的过滤排序逻辑
};

// 鸿蒙
@State selectedShelf: string = '1';
@State bookList: Book[] = initialBooks;

// 派生状态 - 书籍列表
@DerivedState
get currentShelfBooks(): Book[] {
  // 完全复用RN端的过滤排序逻辑
}

// 派生状态 - 统计数据(新增封装)
@DerivedState
get statsData() {
  const books = this.currentShelfBooks;
  return {
    totalPages: books.reduce((sum, book) => sum + book.pages, 0),
    avgRating: books.length > 0 
      ? (books.reduce((sum, book) => sum + book.rating, 0) / books.length).toFixed(1)
      : '0.0',
    latestYear: books.length > 0 
      ? Math.max(...books.map(book => book.publishYear))
      : 0
  };
}

适配优势

  • 自动响应式:派生状态自动响应基础状态变化,无需手动调用;
  • 数据封装:将分散的统计计算封装为统一的 statsData 对象;
  • 代码简化 :渲染时直接使用 this.statsData.totalPages,无需重复计算;
  • 性能优化:派生状态有缓存机制,避免重复计算;
  • 逻辑复用:核心的过滤排序逻辑 100% 复用,零修改。

(2)FlatList 列表

React Native 的 FlatList 替换为鸿蒙的 List + ForEach 组合,并优化了列表性能:

tsx 复制代码
// React Native
<FlatList
  data={currentShelfBooks}
  renderItem={renderBookItem}
  keyExtractor={item => item.id}
  showsVerticalScrollIndicator={false}
/>

// 鸿蒙
List({ space: 0 }) {
  ForEach(this.currentShelfBooks, (item) => {
    ListItem() {
      this.renderBookItem(item);
    }
  }, (item) => item.id);
}
.width('100%')
.shrink(0)
.edgeEffect(EdgeEffect.None)
.scrollBar(BarState.Off);

该书架管理应用的跨端适配实践验证了数据驱动型列表应用从 React Native 向鸿蒙迁移的高效性,核心的业务逻辑和数据模型可实现完全复用,仅需适配UI组件层和交互层。这种适配模式特别适合阅读类、管理类等数据驱动型应用开发,能够显著提升跨端开发效率,同时利用鸿蒙的原生能力提升应用性能和用户体验。


真实演示案例代码:

js 复制代码
// app.tsx
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions, Alert, FlatList } from 'react-native';

// Base64 图标库
const ICONS_BASE64 = {
  home: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  book: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  shelf: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  add: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  search: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  filter: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  sort: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  more: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};

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

// 书籍类型
type Book = {
  id: string;
  title: string;
  author: string;
  genre: string;
  pages: number;
  rating: number;
  publishYear: number;
  description: string;
  shelf: string;
  cover: string;
};

// 书架类型
type Shelf = {
  id: string;
  name: string;
  icon: string;
  bookCount: number;
};

// 模拟数据
const shelves: Shelf[] = [
  { id: '1', name: '已读书籍', icon: '📚', bookCount: 8 },
  { id: '2', name: '想读书籍', icon: '📖', bookCount: 5 },
  { id: '3', name: '正在阅读', icon: '👀', bookCount: 3 },
  { id: '4', name: '技术书籍', icon: '💻', bookCount: 6 },
  { id: '5', name: '小说文学', icon: '📝', bookCount: 7 },
  { id: '6', name: '个人收藏', icon: '❤️', bookCount: 4 },
];

const books: Book[] = [
  {
    id: '1',
    title: '百年孤独',
    author: '加西亚·马尔克斯',
    genre: '魔幻现实主义',
    pages: 360,
    rating: 9.2,
    publishYear: 1967,
    description: '一部描绘布恩迪亚家族七代人传奇故事的魔幻现实主义小说',
    shelf: '已读书籍',
    cover: ''
  },
  {
    id: '2',
    title: '1984',
    author: '乔治·奥威尔',
    genre: '反乌托邦',
    pages: 328,
    rating: 9.4,
    publishYear: 1949,
    description: '一个极权主义社会的寓言,探讨了自由与权力的关系',
    shelf: '已读书籍',
    cover: ''
  },
  {
    id: '3',
    title: '算法导论',
    author: '托马斯·科尔曼',
    genre: '计算机科学',
    pages: 1292,
    rating: 9.0,
    publishYear: 2009,
    description: '计算机算法的经典教材,涵盖广泛的数据结构和算法',
    shelf: '技术书籍',
    cover: ''
  },
  {
    id: '4',
    title: '三体',
    author: '刘慈欣',
    genre: '科幻小说',
    pages: 302,
    rating: 8.8,
    publishYear: 2006,
    description: '中国科幻文学的里程碑之作,讲述人类与三体文明的接触',
    shelf: '已读书籍',
    cover: ''
  },
  {
    id: '5',
    title: '深入理解计算机系统',
    author: '兰德尔·布莱恩特',
    genre: '计算机科学',
    pages: 784,
    rating: 9.5,
    publishYear: 2011,
    description: '从程序员视角了解计算机系统的权威指南',
    shelf: '技术书籍',
    cover: ''
  },
  {
    id: '6',
    title: '活着',
    author: '余华',
    genre: '现代文学',
    pages: 191,
    rating: 9.1,
    publishYear: 1993,
    description: '一个普通人在时代变迁中的苦难历程',
    shelf: '已读书籍',
    cover: ''
  },
  {
    id: '7',
    title: 'JavaScript高级程序设计',
    author: '马特·弗利',
    genre: '编程技术',
    pages: 852,
    rating: 8.7,
    publishYear: 2012,
    description: '前端开发必备经典教材',
    shelf: '技术书籍',
    cover: ''
  },
  {
    id: '8',
    title: '红楼梦',
    author: '曹雪芹',
    genre: '古典文学',
    pages: 1200,
    rating: 9.6,
    publishYear: 1791,
    description: '中国古典小说的巅峰之作',
    shelf: '想读书籍',
    cover: ''
  },
  {
    id: '9',
    title: 'Effective Java',
    author: '约书亚·布洛克',
    genre: '编程技术',
    pages: 416,
    rating: 9.3,
    publishYear: 2017,
    description: 'Java编程的最佳实践指南',
    shelf: '正在阅读',
    cover: ''
  },
  {
    id: '10',
    title: '围城',
    author: '钱钟书',
    genre: '现代文学',
    pages: 311,
    rating: 8.9,
    publishYear: 1947,
    description: '描绘知识分子生活的讽刺小说',
    shelf: '小说文学',
    cover: ''
  },
];

const ReadingApp: React.FC = () => {
  const [selectedShelf, setSelectedShelf] = useState<string>('1');
  const [bookList, setBookList] = useState<Book[]>(books);
  const [sortBy, setSortBy] = useState<'title' | 'author' | 'rating' | 'year'>('title');
  const [searchQuery, setSearchQuery] = useState<string>('');

  // 获取当前书架的书籍
  const getCurrentShelfBooks = () => {
    const shelfName = shelves.find(shelf => shelf.id === selectedShelf)?.name;
    return bookList.filter(book => 
      book.shelf === shelfName &&
      (searchQuery === '' || 
       book.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
       book.author.toLowerCase().includes(searchQuery.toLowerCase()))
    ).sort((a, b) => {
      switch (sortBy) {
        case 'title':
          return a.title.localeCompare(b.title);
        case 'author':
          return a.author.localeCompare(b.author);
        case 'rating':
          return b.rating - a.rating;
        case 'year':
          return b.publishYear - a.publishYear;
        default:
          return 0;
      }
    });
  };

  // 渲染书籍项
  const renderBookItem = ({ item }: { item: Book }) => (
    <View style={styles.bookCard}>
      <View style={styles.coverContainer}>
        <View style={styles.coverPlaceholder}>
          <Text style={styles.coverText}>📖</Text>
        </View>
      </View>
      
      <View style={styles.infoContainer}>
        <View style={styles.headerContainer}>
          <Text style={styles.bookTitle}>{item.title}</Text>
          <Text style={styles.rating}>⭐ {item.rating}</Text>
        </View>
        
        <Text style={styles.author}>作者: {item.author}</Text>
        <Text style={styles.genre}>类型: {item.genre}</Text>
        <Text style={styles.pages}>页数: {item.pages}页</Text>
        <Text style={styles.year}>出版: {item.publishYear}</Text>
        <Text style={styles.description} numberOfLines={2}>{item.description}</Text>
        
        <View style={styles.actionContainer}>
          <TouchableOpacity 
            style={styles.actionButton}
            onPress={() => Alert.alert('阅读', `开始阅读《${item.title}》`)}
          >
            <Text style={styles.actionButtonText}>阅读</Text>
          </TouchableOpacity>
          <TouchableOpacity 
            style={styles.actionButton}
            onPress={() => Alert.alert('编辑', `编辑《${item.title}》信息`)}
          >
            <Text style={styles.actionButtonText}>编辑</Text>
          </TouchableOpacity>
        </View>
      </View>
    </View>
  );

  // 添加新书
  const addNewBook = () => {
    Alert.prompt(
      '添加新书',
      '请输入书名:',
      (value) => {
        if (value) {
          const newShelf = shelves.find(shelf => shelf.id === selectedShelf)?.name || '未知书架';
          const newBook: Book = {
            id: `${bookList.length + 1}`,
            title: value,
            author: '未知作者',
            genre: '其他',
            pages: 0,
            rating: 0,
            publishYear: new Date().getFullYear(),
            description: '新添加的书籍',
            shelf: newShelf,
            cover: ''
          };
          setBookList([newBook, ...bookList]);
        }
      }
    );
  };

  const currentShelfBooks = getCurrentShelfBooks();

  return (
    <SafeAreaView style={styles.container}>
      {/* 头部 */}
      <View style={styles.header}>
        <Text style={styles.title}>我的书架</Text>
        <TouchableOpacity style={styles.addButton} onPress={addNewBook}>
          <Text style={styles.addButtonText}>+</Text>
        </TouchableOpacity>
      </View>

      <ScrollView style={styles.content}>
        {/* 搜索栏 */}
        <View style={styles.searchContainer}>
          <Text style={styles.searchIcon}>🔍</Text>
          <Text style={styles.searchPlaceholder}>搜索书名或作者</Text>
        </View>

        {/* 书架选择 */}
        <Text style={styles.sectionTitle}>选择书架</Text>
        <ScrollView 
          horizontal 
          showsHorizontalScrollIndicator={false} 
          style={styles.shelvesContainer}
        >
          <View style={styles.shelvesList}>
            {shelves.map(shelf => (
              <TouchableOpacity
                key={shelf.id}
                style={[
                  styles.shelfItem,
                  selectedShelf === shelf.id && styles.selectedShelf
                ]}
                onPress={() => setSelectedShelf(shelf.id)}
              >
                <Text style={styles.shelfIcon}>{shelf.icon}</Text>
                <Text style={styles.shelfName}>{shelf.name}</Text>
                <Text style={styles.shelfCount}>{shelf.bookCount}</Text>
              </TouchableOpacity>
            ))}
          </View>
        </ScrollView>

        {/* 排序选项 */}
        <View style={styles.sortContainer}>
          <Text style={styles.sortLabel}>排序方式:</Text>
          <View style={styles.sortOptions}>
            <TouchableOpacity 
              style={[styles.sortOption, sortBy === 'title' && styles.activeSortOption]}
              onPress={() => setSortBy('title')}
            >
              <Text style={styles.sortOptionText}>书名</Text>
            </TouchableOpacity>
            <TouchableOpacity 
              style={[styles.sortOption, sortBy === 'author' && styles.activeSortOption]}
              onPress={() => setSortBy('author')}
            >
              <Text style={styles.sortOptionText}>作者</Text>
            </TouchableOpacity>
            <TouchableOpacity 
              style={[styles.sortOption, sortBy === 'rating' && styles.activeSortOption]}
              onPress={() => setSortBy('rating')}
            >
              <Text style={styles.sortOptionText}>评分</Text>
            </TouchableOpacity>
            <TouchableOpacity 
              style={[styles.sortOption, sortBy === 'year' && styles.activeSortOption]}
              onPress={() => setSortBy('year')}
            >
              <Text style={styles.sortOptionText}>年份</Text>
            </TouchableOpacity>
          </View>
        </View>

        {/* 书架详情 */}
        <View style={styles.shelfDetails}>
          <Text style={styles.shelfDetailTitle}>
            {shelves.find(s => s.id === selectedShelf)?.name || '书架'}详情
          </Text>
          <Text style={styles.shelfBookCount}>
            共 {currentShelfBooks.length} 本书
          </Text>
        </View>

        {/* 书籍列表 */}
        <Text style={styles.sectionTitle}>
          {shelves.find(s => s.id === selectedShelf)?.name || '书架'} ({currentShelfBooks.length})
        </Text>
        
        <FlatList
          data={currentShelfBooks}
          renderItem={renderBookItem}
          keyExtractor={item => item.id}
          showsVerticalScrollIndicator={false}
        />

        {/* 统计信息 */}
        <View style={styles.statsContainer}>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>
              {currentShelfBooks.reduce((sum, book) => sum + book.pages, 0)}
            </Text>
            <Text style={styles.statLabel}>总页数</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>
              {currentShelfBooks.length > 0 
                ? (currentShelfBooks.reduce((sum, book) => sum + book.rating, 0) / currentShelfBooks.length).toFixed(1)
                : '0.0'}
            </Text>
            <Text style={styles.statLabel}>平均评分</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>
              {Math.max(...currentShelfBooks.map(book => book.publishYear), 0)}
            </Text>
            <Text style={styles.statLabel}>最新出版</Text>
          </View>
        </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={() => 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>
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f8fafc',
  },
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    padding: 20,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#e2e8f0',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  addButton: {
    width: 36,
    height: 36,
    borderRadius: 18,
    backgroundColor: '#3b82f6',
    alignItems: 'center',
    justifyContent: 'center',
  },
  addButtonText: {
    fontSize: 20,
    color: '#ffffff',
    fontWeight: 'bold',
  },
  content: {
    flex: 1,
    padding: 16,
  },
  searchContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#ffffff',
    borderRadius: 20,
    paddingVertical: 12,
    paddingHorizontal: 16,
    marginBottom: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  searchIcon: {
    fontSize: 18,
    color: '#64748b',
  },
  searchPlaceholder: {
    fontSize: 14,
    color: '#94a3b8',
    marginLeft: 12,
    flex: 1,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#1e293b',
    marginVertical: 12,
  },
  shelvesContainer: {
    marginBottom: 16,
  },
  shelvesList: {
    flexDirection: 'row',
  },
  shelfItem: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 12,
    marginRight: 12,
    alignItems: 'center',
    width: 80,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  selectedShelf: {
    backgroundColor: '#3b82f6',
  },
  shelfIcon: {
    fontSize: 24,
    marginBottom: 4,
  },
  shelfName: {
    fontSize: 12,
    color: '#64748b',
    fontWeight: '500',
    marginBottom: 2,
  },
  shelfCount: {
    fontSize: 10,
    color: '#94a3b8',
  },
  sortContainer: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  sortLabel: {
    fontSize: 14,
    color: '#64748b',
    marginBottom: 8,
  },
  sortOptions: {
    flexDirection: 'row',
    flexWrap: 'wrap',
  },
  sortOption: {
    backgroundColor: '#f1f5f9',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 20,
    marginRight: 8,
    marginBottom: 8,
  },
  activeSortOption: {
    backgroundColor: '#3b82f6',
  },
  sortOptionText: {
    fontSize: 12,
    color: '#3b82f6',
    fontWeight: '500',
  },
  activeSortOptionText: {
    color: '#ffffff',
  },
  shelfDetails: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  shelfDetailTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  shelfBookCount: {
    fontSize: 14,
    color: '#64748b',
  },
  bookCard: {
    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: {
    marginRight: 12,
  },
  coverPlaceholder: {
    width: 60,
    height: 80,
    backgroundColor: '#e2e8f0',
    borderRadius: 4,
    alignItems: 'center',
    justifyContent: 'center',
  },
  coverText: {
    fontSize: 24,
  },
  infoContainer: {
    flex: 1,
  },
  headerContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 4,
  },
  bookTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    flex: 1,
  },
  rating: {
    fontSize: 14,
    color: '#f59e0b',
    fontWeight: 'bold',
  },
  author: {
    fontSize: 12,
    color: '#64748b',
    marginBottom: 2,
  },
  genre: {
    fontSize: 12,
    color: '#64748b',
    marginBottom: 2,
  },
  pages: {
    fontSize: 12,
    color: '#94a3b8',
    marginBottom: 2,
  },
  year: {
    fontSize: 12,
    color: '#94a3b8',
    marginBottom: 4,
  },
  description: {
    fontSize: 14,
    color: '#64748b',
    marginBottom: 8,
  },
  actionContainer: {
    flexDirection: 'row',
  },
  actionButton: {
    backgroundColor: '#f1f5f9',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 6,
    marginRight: 8,
  },
  actionButtonText: {
    fontSize: 12,
    color: '#3b82f6',
    fontWeight: '500',
  },
  statsContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  statItem: {
    alignItems: 'center',
  },
  statNumber: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#3b82f6',
  },
  statLabel: {
    fontSize: 12,
    color: '#64748b',
    marginTop: 4,
  },
  infoCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 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,
  },
  navText: {
    fontSize: 12,
    color: '#94a3b8',
  },
});

export default ReadingApp;

打包

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

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

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

本文探讨了一个基于React Native的跨平台阅读应用实现方案。该应用采用组件化架构,包含书籍展示、分类筛选、搜索排序等功能模块,通过清晰的状态管理实现数据筛选与排序。文章详细分析了应用的数据结构、UI布局和交互设计,特别关注了跨平台兼容性问题,提出了使用核心组件、统一样式定义等适配策略。此外,还从渲染优化和计算效率角度探讨了性能提升方法,如使用FlatList优化长列表渲染,以及采用useReducer管理复杂状态等优化建议。整体实现注重代码可维护性和用户体验,为开发跨平台阅读应用提供了实用参考。

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

相关推荐
听麟2 小时前
HarmonyOS 6.0+ PC端多设备文件拖拽协同开发实战:手眼同行增强与分布式软总线深度应用
分布式·华为·harmonyos
BlackWolfSky2 小时前
鸿蒙中级课程笔记11—元服务开发
笔记·华为·harmonyos
xiaoqi9222 小时前
React Native鸿蒙跨平台实现图片画廊类页面是视觉展示型APP(如摄影类、图库类、设计类APP)的核心载体,其核心需求是实现图片的流畅渲染
javascript·react native·react.js·ecmascript·harmonyos
Highcharts.js2 小时前
Next.js 集成 Highcharts 官网文档说明(2025 新版)
开发语言·前端·javascript·react.js·开发文档·next.js·highcharts
灰灰勇闯IT3 小时前
Flutter for OpenHarmony:响应式布局(LayoutBuilder / MediaQuery)—— 构建真正自适应的鸿蒙应用
flutter·华为·harmonyos
小哥Mark3 小时前
在鸿蒙应用工程中可以使用哪些Flutter手势交互组件实现点击、双击、长按、拖动、缩放、滑动等多种手势
flutter·交互·harmonyos
小哥Mark3 小时前
使用Flutter导航组件TabBar、AppBar等为鸿蒙应用程序构建完整的应用导航体系
flutter·harmonyos·鸿蒙
妙团团3 小时前
React学习之自定义tab组合组件
javascript·学习·react.js
前端世界3 小时前
鸿蒙分布式网络性能优化实战:从通信建连到多设备协同
网络·分布式·harmonyos