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

组件化

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

  • 主应用组件 (GameLibraryApp) - 负责整体布局和状态管理
  • Amiibo 列表渲染 - 负责渲染 A miibo 卡片列表
  • 系列筛选 - 提供按系列筛选 A miibo 的功能
  • 搜索功能 - 提供搜索 A miibo 的功能
  • 排序功能 - 提供按名称、日期、价格排序的功能
  • 筛选功能 - 提供按拥有状态筛选的功能

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

状态管理

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

const [collection, setCollection] = useState<Amiibo[]>(amiibos);
const [activeSeries, setActiveSeries] = useState<string>('1');
const [searchQuery, setSearchQuery] = useState<string>('');
const [sortBy, setSortBy] = useState<'name' | 'date' | 'price'>('name');
const [filterOwned, setFilterOwned] = useState<boolean>(false);

这种状态管理方式简洁高效,通过状态更新触发组件重新渲染,实现了 A miibo 的筛选、搜索、排序等功能。使用 TypeScript 类型定义确保了数据结构的类型安全,提高了代码的可靠性。


数据筛选

应用实现了复杂的数据筛选和处理逻辑:

// 获取当前系列的Amiibo
const getCurrentSeriesAmiibos = () => {
  const seriesName = amiiboSeries.find(series => series.id === activeSeries)?.name;
  return collection.filter(amiibo =>
    amiibo.series === seriesName &&
    (searchQuery === '' || amiibo.name.toLowerCase().includes(searchQuery.toLowerCase())) &&
    (!filterOwned || amiibo.owned)
  );
};

// 按排序方式整理数据
const sortedAmiibos = getCurrentSeriesAmiibos().sort((a, b) => {
  switch (sortBy) {
    case 'name':
      return a.name.localeCompare(b.name);
    case 'date':
      return new Date(a.releaseDate).getTime() - new Date(b.releaseDate).getTime();
    case 'price':
      return a.price - b.price;
    default:
      return 0;
  }
});

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

  • 系列筛选 - 只显示当前选中系列的 A miibo
  • 搜索筛选 - 根据搜索关键字筛选 A miibo
  • 拥有状态筛选 - 只显示已拥有的 A miibo
  • 排序 - 按名称、日期或价格排序

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

状态

应用实现了 A miibo 拥有状态的管理:

// 切换拥有状态
const toggleOwned = (id: string) => {
  setCollection(collection.map(amiibo =>
    amiibo.id === id ? { ...amiibo, owned: !amiibo.owned } : amiibo
  ));
};

这种实现方式通过修改 A miibo 的 owned 属性,实现了拥有状态的切换。当用户点击 A miibo 卡片时,可以轻松切换其拥有状态。


应用实现了 A miibo 稀有度的视觉化:

// 获取稀有度颜色
const getRarityColor = (rarity: string) => {
  switch (rarity) {
    case 'common': return '#9ca3af';
    case 'rare': return '#3b82f6';
    case 'epic': return '#8b5cf6';
    case 'legendary': return '#f59e0b';
    default: return '#9ca3af';
  }
};

这种实现方式根据 A miibo 的稀有度返回不同的颜色,使得用户可以通过颜色直观地区分不同稀有度的 A miibo。


类型定义

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

  1. Amiibo - A miibo 类型,包含 A miibo 的完整信息,如 ID、名称、角色、系列、发布日期、价格、拥有状态、稀有度和图片
  2. Series - 系列类型,包含系列的 ID、名称和图标

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


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

  • amiiboSeries - 系列列表
  • amiibos - A miibo 列表
  • collection - 当前管理的 A miibo 集合
  • activeSeries - 当前选中的系列
  • searchQuery - 搜索关键字
  • sortBy - 排序方式
  • filterOwned - 是否筛选已拥有的 A miibo

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


布局结构

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

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

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

交互设计

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

  • 系列筛选 - 点击系列切换显示的 A miibo
  • 搜索 - 输入关键字搜索 A miibo
  • 排序 - 点击排序按钮切换排序方式
  • 筛选 - 点击筛选按钮切换是否只显示已拥有的 A miibo
  • 拥有状态 - 点击 A miibo 卡片切换拥有状态

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


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

// 优化前
<FlatList
  data={sortedAmiibos}
  renderItem={({ item }) => (
    <AmiiboCard amiibo={item} />
  )}
  keyExtractor={item => item.id}
/>

// 优化后
<FlatList
  data={sortedAmiibos}
  renderItem={({ item }) => (
    <AmiiboCard amiibo={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 [collection, setCollection] = useState<Amiibo[]>(amiibos);
const [activeSeries, setActiveSeries] = useState<string>('1');
const [searchQuery, setSearchQuery] = useState<string>('');
const [sortBy, setSortBy] = useState<'name' | 'date' | 'price'>('name');
const [filterOwned, setFilterOwned] = useState<boolean>(false);

// 优化后
type AppState = {
  collection: Amiibo[];
  activeSeries: string;
  searchQuery: string;
  sortBy: 'name' | 'date' | 'price';
  filterOwned: boolean;
};

type AppAction =
  | { type: 'SET_COLLECTION'; payload: Amiibo[] }
  | { type: 'SET_ACTIVE_SERIES'; payload: string }
  | { type: 'SET_SEARCH_QUERY'; payload: string }
  | { type: 'SET_SORT_BY'; payload: 'name' | 'date' | 'price' }
  | { type: 'SET_FILTER_OWNED'; payload: boolean }
  | { type: 'TOGGLE_OWNED'; payload: string };

const initialState: AppState = {
  collection: amiibos,
  activeSeries: '1',
  searchQuery: '',
  sortBy: 'name',
  filterOwned: false
};

const appReducer = (state: AppState, action: AppAction): AppState => {
  switch (action.type) {
    case 'SET_COLLECTION':
      return { ...state, collection: action.payload };
    case 'SET_ACTIVE_SERIES':
      return { ...state, activeSeries: action.payload };
    case 'SET_SEARCH_QUERY':
      return { ...state, searchQuery: action.payload };
    case 'SET_SORT_BY':
      return { ...state, sortBy: action.payload };
    case 'SET_FILTER_OWNED':
      return { ...state, filterOwned: action.payload };
    case 'TOGGLE_OWNED':
      return {
        ...state,
        collection: state.collection.map(amiibo =>
          amiibo.id === action.payload ? { ...amiibo, owned: !amiibo.owned } : amiibo
        )
      };
    default:
      return state;
  }
};

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

3. 数据持久化

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

import AsyncStorage from '@react-native-async-storage/async-storage';

const STORAGE_KEYS = {
  COLLECTION: '@amiibo_collection',
  SETTINGS: '@app_settings',
};

const GameLibraryApp = () => {
  const [collection, setCollection] = useState<Amiibo[]>(amiibos);
  const [settings, setSettings] = useState({
    activeSeries: '1',
    sortBy: 'name' as 'name' | 'date' | 'price',
    filterOwned: false,
  });

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

  const loadData = async () => {
    try {
      const storedCollection = await AsyncStorage.getItem(STORAGE_KEYS.COLLECTION);
      const storedSettings = await AsyncStorage.getItem(STORAGE_KEYS.SETTINGS);

      if (storedCollection) {
        setCollection(JSON.parse(storedCollection));
      }

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

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

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

  // 其他代码...
};

4. 导航系统

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

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

const Stack = createStackNavigator();

const App = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen 
          name="Home" 
          component={GameLibraryApp} 
          options={{ title: '游戏收藏库' }} 
        />
        <Stack.Screen 
          name="AmiiboDetail" 
          component={AmiiboDetailScreen} 
          options={({ route }) => ({ title: route.params?.amiiboName || 'Amiibo详情' })} 
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
};

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

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


理解这个功能完整的 React Native Amiibo 收藏库应用的技术实现逻辑,同时掌握其向鸿蒙(HarmonyOS)平台跨端适配的核心思路与具体实现方案。该应用涵盖了数据筛选、排序、状态管理、UI组件封装等移动端开发的核心场景,是典型的分类列表型应用架构,非常适合作为跨端开发的学习案例。

该 Amiibo 收藏库应用采用状态驱动+数据过滤排序的现代化 React Native 架构,核心数据模型设计体现了收藏类应用的典型特征:

// Amiibo核心数据模型 - 覆盖收藏类应用核心属性
type Amiibo = {
  id: string;                  // 唯一标识
  name: string;                // 名称
  character: string;           // 角色名
  series: string;              // 所属系列
  releaseDate: string;         // 发行日期
  price: number;               // 价格
  owned: boolean;              // 拥有状态
  rarity: 'common' | 'rare' | 'epic' | 'legendary'; // 稀有度
  image: string;               // 图片地址
};

// 系列分类模型 - 用于分类筛选
type Series = {
  id: string;
  name: string;
  icon: string;                // 系列图标(emoji形式)
};

数据模型设计亮点

  • 类型完整性:涵盖收藏类应用所需的核心属性,包括基础信息、状态标识、分类标签;
  • 枚举型稀有度:使用联合类型限定稀有度取值范围,保证数据规范性;
  • 布尔状态标识owned 字段清晰标识收藏状态,便于状态切换;
  • 分类体系:独立的 Series 模型支持多维度筛选,符合内容分类的最佳实践。

(1)数据筛选

应用的核心价值在于多维度的数据筛选和排序能力,这是收藏类应用的核心功能:

// 核心状态定义 - 控制筛选和排序行为
const [collection, setCollection] = useState<Amiibo[]>(amiibos);
const [activeSeries, setActiveSeries] = useState<string>('1');
const [searchQuery, setSearchQuery] = useState<string>('');
const [sortBy, setSortBy] = useState<'name' | 'date' | 'price'>('name');
const [filterOwned, setFilterOwned] = useState<boolean>(false);

// 多条件数据筛选逻辑
const getCurrentSeriesAmiibos = () => {
  const seriesName = amiiboSeries.find(series => series.id === activeSeries)?.name;
  return collection.filter(amiibo => 
    // 系列筛选
    amiibo.series === seriesName &&
    // 搜索筛选(不区分大小写)
    (searchQuery === '' || amiibo.name.toLowerCase().includes(searchQuery.toLowerCase())) &&
    // 拥有状态筛选
    (!filterOwned || amiibo.owned)
  );
};

// 多维度排序逻辑
const sortedAmiibos = getCurrentSeriesAmiibos().sort((a, b) => {
  switch (sortBy) {
    case 'name':
      return a.name.localeCompare(b.name); // 按名称字母排序
    case 'date':
      return new Date(a.releaseDate).getTime() - new Date(b.releaseDate).getTime(); // 按日期排序
    case 'price':
      return a.price - b.price; // 按价格排序
    default:
      return 0;
  }
});

筛选排序设计要点

  • 组合筛选:支持系列、关键词、拥有状态的多条件组合筛选;
  • 类型安全:排序字段使用联合类型限定,避免非法值;
  • 用户体验优化:搜索筛选支持大小写不敏感匹配;
  • 性能考量:筛选和排序逻辑分离,便于维护和扩展;
  • 纯函数设计:筛选和排序函数无副作用,便于测试和复用。
(2)状态管理
// 切换拥有状态(不可变更新)
const toggleOwned = (id: string) => {
  setCollection(collection.map(amiibo => 
    amiibo.id === id ? { ...amiibo, owned: !amiibo.owned } : amiibo
  ));
};

// 稀有度颜色映射(视觉化展示)
const getRarityColor = (rarity: string) => {
  switch (rarity) {
    case 'common': return '#9ca3af';    // 普通 - 灰色
    case 'rare': return '#3b82f6';      // 稀有 - 蓝色
    case 'epic': return '#8b5cf6';      // 史诗 - 紫色
    case 'legendary': return '#f59e0b'; // 传说 - 金色
    default: return '#9ca3af';
  }
};

状态管理设计亮点

  • 不可变更新:使用数组 map 和对象展开语法实现状态的不可变更新,符合 React 最佳实践;
  • 视觉映射:稀有度与颜色的映射函数,实现数据到视觉的统一转换;
  • 单一职责:每个函数只负责单一功能,便于维护和扩展。

(1)分类标签栏实现

实现了移动端经典的横向滚动分类标签栏,支持选中状态切换:

<ScrollView 
  horizontal 
  showsHorizontalScrollIndicator={false} 
  style={styles.seriesContainer}
>
  <View style={styles.seriesList}>
    {amiiboSeries.map(series => (
      <TouchableOpacity
        key={series.id}
        style={[
          styles.seriesItem,
          activeSeries === series.id && styles.activeSeries
        ]}
        onPress={() => setActiveSeries(series.id)}
      >
        <Text style={[
          styles.seriesIcon,
          activeSeries === series.id && styles.activeSeriesIcon
        ]}>
          {series.icon}
        </Text>
        <Text style={[
          styles.seriesText,
          activeSeries === series.id && styles.activeSeriesText
        ]}>
          {series.name}
        </Text>
      </TouchableOpacity>
    ))}
  </View>
</ScrollView>
(2)筛选与排序
<View style={styles.filtersContainer}>
  {/* 拥有状态筛选按钮 */}
  <TouchableOpacity 
    style={[styles.filterButton, filterOwned && styles.activeFilter]}
    onPress={() => setFilterOwned(!filterOwned)}
  >
    <Text style={styles.filterText}>只看拥有</Text>
  </TouchableOpacity>
  
  {/* 排序选项组 */}
  <View style={styles.sortContainer}>
    <Text style={styles.sortLabel}>排序:</Text>
    <TouchableOpacity 
      style={[styles.sortButton, sortBy === 'name' && styles.activeSort]}
      onPress={() => setSortBy('name')}
    >
      <Text style={styles.sortText}>名称</Text>
    </TouchableOpacity>
    <TouchableOpacity 
      style={[styles.sortButton, sortBy === 'date' && styles.activeSort]}
      onPress={() => setSortBy('date')}
    >
      <Text style={styles.sortText}>日期</Text>
    </TouchableOpacity>
    <TouchableOpacity 
      style={[styles.sortButton, sortBy === 'price' && styles.activeSort]}
      onPress={() => setSortBy('price')}
    >
      <Text style={styles.sortText}>价格</Text>
    </TouchableOpacity>
  </View>
</View>
(3)数据统计卡片
<View style={styles.statsContainer}>
  <View style={styles.statItem}>
    <Text style={styles.statNumber}>{sortedAmiibos.length}</Text>
    <Text style={styles.statLabel}>当前系列</Text>
  </View>
  <View style={styles.statItem}>
    <Text style={styles.statNumber}>{collection.filter(a => a.owned).length}</Text>
    <Text style={styles.statLabel}>已拥有</Text>
  </View>
  <View style={styles.statItem}>
    <Text style={styles.statNumber}>{collection.filter(a => !a.owned).length}</Text>
    <Text style={styles.statLabel}>未拥有</Text>
  </View>
  <View style={styles.statItem}>
    <Text style={styles.statNumber}>10</Text>
    <Text style={styles.statLabel}>总计</Text>
  </View>
</View>
(4)Amiibo列表项
const renderAmiiboItem = ({ item }: { item: Amiibo }) => (
  <View style={styles.amiiboItem}>
    {/* 图片占位区 */}
    <View style={styles.imageContainer}>
      <View style={styles.imagePlaceholder}>
        <Text style={styles.imageText}> toy </Text>
      </View>
    </View>
    
    {/* 信息区域 */}
    <View style={styles.infoContainer}>
      {/* 名称和拥有状态 */}
      <View style={styles.headerContainer}>
        <Text style={styles.name}>{item.name}</Text>
        <TouchableOpacity 
          style={styles.ownedButton} 
          onPress={() => toggleOwned(item.id)}
        >
          <Text style={styles.ownedText}>{item.owned ? '✅' : '⬜'}</Text>
        </TouchableOpacity>
      </View>
      
      {/* 基础信息 */}
      <Text style={styles.character}>{item.character}</Text>
      <Text style={styles.series}>{item.series}</Text>
      
      {/* 详情信息 */}
      <View style={styles.detailsContainer}>
        <Text style={styles.releaseDate}>发行: {item.releaseDate}</Text>
        <Text style={styles.price}>¥{item.price.toFixed(2)}</Text>
      </View>
      
      {/* 稀有度标签(动态颜色) */}
      <View style={styles.rarityContainer}>
        <Text style={[styles.rarity, { color: getRarityColor(item.rarity) }]}>
          {item.rarity === 'common' ? '普通' : item.rarity === 'rare' ? '稀有' : item.rarity === 'epic' ? '史诗' : '传说'}
        </Text>
      </View>
    </View>
  </View>
);

将该 React Native Amiibo 收藏库应用适配到鸿蒙平台,核心是将 React 的状态管理、数据筛选排序、列表渲染等核心能力映射到鸿蒙 ArkTS + ArkUI 生态,以下是完整的适配方案。

1. 核心技术栈映射

React Native 核心能力 鸿蒙 ArkTS 对应实现 适配关键说明
useState 状态管理 @State/@Link 状态声明语法替换
FlatList 列表渲染 List + LazyForEach 高性能列表渲染
ScrollView (横向) Scroll + scrollDirection 横向滚动容器替换
TouchableOpacity Button + stateEffect(false) 可点击组件替换
StyleSheet.create @Styles/@Extend + 内联样式 样式体系重构
Alert.alert AlertDialog 组件 弹窗交互替换
条件样式(数组语法) 三元运算符 + 内联样式 样式条件判断适配
Dimensions 尺寸获取 @ohos.window API 屏幕尺寸获取适配

2. 鸿蒙端

// index.ets - 鸿蒙端Amiibo收藏库完整实现
import router from '@ohos.router';
import window from '@ohos.window';

// Amiibo类型定义(与RN端保持一致)
type Amiibo = {
  id: string;
  name: string;
  character: string;
  series: string;
  releaseDate: string;
  price: number;
  owned: boolean;
  rarity: 'common' | 'rare' | 'epic' | 'legendary';
  image: string;
};

// 系列类型定义(与RN端保持一致)
type Series = {
  id: string;
  name: string;
  icon: string;
};

// 模拟数据(与RN端保持一致)
const amiiboSeries: Series[] = [
  { id: '1', name: '超级马里奥', icon: '🍄' },
  { id: '2', name: '塞尔达传说', icon: '⚔️' },
  { id: '3', name: '宝可梦', icon: '⚡' },
  { id: '4', name: '动物森友会', icon: '🌳' },
  { id: '5', name: '火焰纹章', icon: '🗡️' },
  { id: '6', name: '大金刚', icon: '🍌' },
  { id: '7', name: '星之卡比', icon: '💫' },
  { id: '8', name: '任天堂明星大乱斗', icon: '🎮' },
];

const amiibos: Amiibo[] = [
  {
    id: '1',
    name: '马里奥',
    character: '马里奥',
    series: '超级马里奥',
    releaseDate: '2014-11-21',
    price: 19.99,
    owned: true,
    rarity: 'common',
    image: ''
  },
  {
    id: '2',
    name: '路易吉',
    character: '路易吉',
    series: '超级马里奥',
    releaseDate: '2014-11-21',
    price: 19.99,
    owned: false,
    rarity: 'common',
    image: ''
  },
  {
    id: '3',
    name: '桃花公主',
    character: '桃花公主',
    series: '超级马里奥',
    releaseDate: '2014-11-21',
    price: 24.99,
    owned: true,
    rarity: 'rare',
    image: ''
  },
  {
    id: '4',
    name: '林克',
    character: '林克',
    series: '塞尔达传说',
    releaseDate: '2014-11-21',
    price: 29.99,
    owned: false,
    rarity: 'epic',
    image: ''
  },
  {
    id: '5',
    name: '塞尔达',
    character: '塞尔达',
    series: '塞尔达传说',
    releaseDate: '2014-11-21',
    price: 29.99,
    owned: true,
    rarity: 'epic',
    image: ''
  },
  {
    id: '6',
    name: '皮卡丘',
    character: '皮卡丘',
    series: '宝可梦',
    releaseDate: '2014-11-21',
    price: 19.99,
    owned: false,
    rarity: 'rare',
    image: ''
  },
  {
    id: '7',
    name: '杰尼龟',
    character: '杰尼龟',
    series: '宝可梦',
    releaseDate: '2014-11-21',
    price: 19.99,
    owned: true,
    rarity: 'common',
    image: ''
  },
  {
    id: '8',
    name: '提米',
    character: '提米',
    series: '动物森友会',
    releaseDate: '2014-11-21',
    price: 14.99,
    owned: true,
    rarity: 'common',
    image: ''
  },
  {
    id: '9',
    name: '罗宾',
    character: '罗宾',
    series: '火焰纹章',
    releaseDate: '2014-11-21',
    price: 24.99,
    owned: false,
    rarity: 'rare',
    image: ''
  },
  {
    id: '10',
    name: '大金刚',
    character: '大金刚',
    series: '大金刚',
    releaseDate: '2014-11-21',
    price: 24.99,
    owned: true,
    rarity: 'epic',
    image: ''
  },
];

@Entry
@Component
struct GameLibraryApp {
  // 核心状态管理(对应RN的useState)
  @State collection: Amiibo[] = amiibos;
  @State activeSeries: string = '1';
  @State searchQuery: string = '';
  @State sortBy: 'name' | 'date' | 'price' = 'name';
  @State filterOwned: boolean = false;
  
  // 屏幕尺寸(对应RN的Dimensions)
  @State windowWidth: number = 0;
  @State windowHeight: number = 0;

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

  // 组件生命周期(获取屏幕尺寸)
  async aboutToAppear() {
    const windowClass = await window.getLastWindow(getContext());
    const windowSize = await windowClass.getWindowProperties();
    this.windowWidth = windowSize.windowRect.width;
    this.windowHeight = windowSize.windowRect.height;
  }

  // 获取当前系列的Amiibo(逻辑与RN完全一致)
  private getCurrentSeriesAmiibos(): Amiibo[] {
    const seriesName = amiiboSeries.find(series => series.id === this.activeSeries)?.name;
    return this.collection.filter(amiibo => 
      amiibo.series === seriesName &&
      (this.searchQuery === '' || amiibo.name.toLowerCase().includes(this.searchQuery.toLowerCase())) &&
      (!this.filterOwned || amiibo.owned)
    );
  }

  // 按排序方式整理数据(逻辑与RN完全一致)
  private get sortedAmiibos(): Amiibo[] {
    return this.getCurrentSeriesAmiibos().sort((a, b) => {
      switch (this.sortBy) {
        case 'name':
          return a.name.localeCompare(b.name);
        case 'date':
          return new Date(a.releaseDate).getTime() - new Date(b.releaseDate).getTime();
        case 'price':
          return a.price - b.price;
        default:
          return 0;
      }
    });
  }

  // 切换拥有状态(逻辑与RN完全一致)
  private toggleOwned(id: string) {
    this.collection = this.collection.map(amiibo => 
      amiibo.id === id ? { ...amiibo, owned: !amiibo.owned } : amiibo
    );
  }

  // 获取稀有度颜色(逻辑与RN完全一致)
  private getRarityColor(rarity: string): string {
    switch (rarity) {
      case 'common': return '#9ca3af';
      case 'rare': return '#3b82f6';
      case 'epic': return '#8b5cf6';
      case 'legendary': return '#f59e0b';
      default: return '#9ca3af';
    }
  }

  // 渲染Amiibo项(Builder函数替换RN的renderAmiiboItem)
  @Builder
  renderAmiiboItem(item: Amiibo) {
    Row({ space: 12 }) {
      // 图片占位区
      Column() {
        Stack() {
          Text(' toy ')
            .fontSize(24)
            .textAlign(TextAlign.Center);
        }
        .width(80)
        .height(80)
        .backgroundColor('#e2e8f0')
        .borderRadius(8)
        .justifyContent(FlexAlign.Center);
      }
      
      // 信息区域
      Column({ space: 4 }) {
        // 名称和拥有状态
        Row({ space: 0 }) {
          Text(item.name)
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#1e293b')
            .flex(1);
          
          Button(item.owned ? '✅' : '⬜')
            .backgroundColor(Color.Transparent)
            .fontSize(18)
            .stateEffect(true)
            .marginLeft(8)
            .onClick(() => this.toggleOwned(item.id));
        }
        
        // 角色名
        Text(item.character)
          .fontSize(14)
          .fontColor('#3b82f6');
        
        // 系列名
        Text(item.series)
          .fontSize(12)
          .fontColor('#64748b');
        
        // 详情信息
        Row({ space: 0 }) {
          Text(`发行: ${item.releaseDate}`)
            .fontSize(12)
            .fontColor('#94a3b8');
          
          Text(`¥${item.price.toFixed(2)}`)
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#10b981')
            .marginLeft('auto');
        }
        
        // 稀有度标签
        Text(item.rarity === 'common' ? '普通' : item.rarity === 'rare' ? '稀有' : item.rarity === 'epic' ? '史诗' : '传说')
          .fontSize(12)
          .fontWeight(FontWeight.Medium)
          .fontColor(this.getRarityColor(item.rarity))
          .alignSelf(ItemAlign.Start);
      }
      .flex(1);
    }
    .backgroundColor('#ffffff')
    .borderRadius(12)
    .padding(12)
    .marginBottom(12)
    .cardShadow();
  }

  // 空状态渲染
  @Builder
  renderEmptyState() {
    Column({ space: 8 }) {
      Text('暂无数据')
        .fontSize(16)
        .fontColor('#64748b');
      
      Text('请调整筛选条件查看')
        .fontSize(14)
        .fontColor('#94a3b8')
        .textAlign(TextAlign.Center);
    }
    .width('100%')
    .paddingVertical(60)
    .alignItems(ItemAlign.Center);
  }

  build() {
    Column({ space: 0 }) {
      // 头部组件
      this.Header();
      
      // 内容区域
      Scroll() {
        Column({ space: 16 }) {
          // 搜索栏
          this.SearchBar();

          // 系列分类
          this.SeriesCategory();

          // 筛选和排序选项
          this.FiltersAndSort();

          // 收藏统计
          this.StatsCard();

          // Amiibo列表标题
          Text(`${amiiboSeries.find(s => s.id === this.activeSeries)?.name || 'Amiibo'} (${this.sortedAmiibos.length})`)
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor('#1e293b')
            .marginVertical(12)
            .width('100%');
          
          // Amiibo列表
          if (this.sortedAmiibos.length > 0) {
            List() {
              LazyForEach(new AmiiboDataSource(this.sortedAmiibos), (item: Amiibo) => {
                ListItem() {
                  this.renderAmiiboItem(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('Amiibo收藏库')
        .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
  SearchBar() {
    Row({ space: 12 }) {
      Text('🔍')
        .fontSize(18)
        .fontColor('#64748b');
      
      Text('搜索Amiibo名称')
        .fontSize(14)
        .fontColor('#94a3b8')
        .flex(1);
    }
    .backgroundColor('#ffffff')
    .borderRadius(20)
    .padding({ top: 12, bottom: 12, left: 16, right: 16 })
    .cardShadow()
    .width('100%');
  }

  // 系列分类标签栏
  @Builder
  SeriesCategory() {
    Column({ space: 0 }) {
      Text('Amiibo系列')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1e293b')
        .marginVertical(12)
        .width('100%');
      
      Scroll({ scrollDirection: ScrollDirection.Horizontal }) {
        Row({ space: 12 }) {
          ForEach(amiiboSeries, (series: Series) => {
            Button()
              .backgroundColor(this.activeSeries === series.id ? '#3b82f6' : '#ffffff')
              .borderRadius(20)
              .padding({ top: 8, bottom: 8, left: 16, right: 16 })
              .stateEffect(true)
              .cardShadow()
              .onClick(() => this.activeSeries = series.id) {
                Column({ space: 4 }) {
                  Text(series.icon)
                    .fontSize(16)
                    .fontColor(this.activeSeries === series.id ? '#ffffff' : '#64748b');
                  
                  Text(series.name)
                    .fontSize(12)
                    .fontColor(this.activeSeries === series.id ? '#ffffff' : '#64748b')
                    .fontWeight(this.activeSeries === series.id ? FontWeight.Medium : FontWeight.Normal);
                }
              };
          });
        }
        .width('auto');
      }
      .scrollBar(BarState.Off);
    }
    .width('100%');
  }

  // 筛选和排序选项
  @Builder
  FiltersAndSort() {
    Column({ space: 12 }) {
      // 拥有状态筛选
      Button('只看拥有')
        .backgroundColor(this.filterOwned ? '#3b82f6' : '#f1f5f9')
        .borderRadius(20)
        .padding({ top: 6, bottom: 6, left: 12, right: 12 })
        .fontSize(12)
        .fontColor('#3b82f6')
        .fontWeight(FontWeight.Medium)
        .stateEffect(true)
        .alignSelf(ItemAlign.Start)
        .onClick(() => this.filterOwned = !this.filterOwned);
      
      // 排序选项
      Row({ space: 8 }) {
        Text('排序:')
          .fontSize(14)
          .fontColor('#64748b');
        
        // 名称排序
        Button('名称')
          .backgroundColor(this.sortBy === 'name' ? '#3b82f6' : '#f1f5f9')
          .borderRadius(20)
          .padding({ top: 6, bottom: 6, left: 12, right: 12 })
          .fontSize(12)
          .fontColor('#3b82f6')
          .stateEffect(true)
          .onClick(() => this.sortBy = 'name');
        
        // 日期排序
        Button('日期')
          .backgroundColor(this.sortBy === 'date' ? '#3b82f6' : '#f1f5f9')
          .borderRadius(20)
          .padding({ top: 6, bottom: 6, left: 12, right: 12 })
          .fontSize(12)
          .fontColor('#3b82f6')
          .stateEffect(true)
          .onClick(() => this.sortBy = 'date');
        
        // 价格排序
        Button('价格')
          .backgroundColor(this.sortBy === 'price' ? '#3b82f6' : '#f1f5f9')
          .borderRadius(20)
          .padding({ top: 6, bottom: 6, left: 12, right: 12 })
          .fontSize(12)
          .fontColor('#3b82f6')
          .stateEffect(true)
          .onClick(() => this.sortBy = 'price');
      }
      .width('100%')
      .justifyContent(FlexAlign.Start);
    }
    .backgroundColor('#ffffff')
    .borderRadius(12)
    .padding(12)
    .cardShadow()
    .width('100%');
  }

  // 收藏统计卡片
  @Builder
  StatsCard() {
    Row({ space: 8 }) {
      // 当前系列
      Column({ space: 4 }) {
        Text(`${this.sortedAmiibos.length}`)
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3b82f6');
        
        Text('当前系列')
          .fontSize(12)
          .fontColor('#64748b');
      }
      .backgroundColor('#ffffff')
      .borderRadius(12)
      .padding(12)
      .alignItems(ItemAlign.Center)
      .flex(1)
      .cardShadow();
      
      // 已拥有
      Column({ space: 4 }) {
        Text(`${this.collection.filter(a => a.owned).length}`)
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3b82f6');
        
        Text('已拥有')
          .fontSize(12)
          .fontColor('#64748b');
      }
      .backgroundColor('#ffffff')
      .borderRadius(12)
      .padding(12)
      .alignItems(ItemAlign.Center)
      .flex(1)
      .cardShadow();
      
      // 未拥有
      Column({ space: 4 }) {
        Text(`${this.collection.filter(a => !a.owned).length}`)
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3b82f6');
        
        Text('未拥有')
          .fontSize(12)
          .fontColor('#64748b');
      }
      .backgroundColor('#ffffff')
      .borderRadius(12)
      .padding(12)
      .alignItems(ItemAlign.Center)
      .flex(1)
      .cardShadow();
      
      // 总计
      Column({ space: 4 }) {
        Text('10')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3b82f6');
        
        Text('总计')
          .fontSize(12)
          .fontColor('#64748b');
      }
      .backgroundColor('#ffffff')
      .borderRadius(12)
      .padding(12)
      .alignItems(ItemAlign.Center)
      .flex(1)
      .cardShadow();
    }
    .width('100%');
  }

  // 使用说明卡片
  @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(() => {
          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');
          }
        };
    }
    .backgroundColor('#ffffff')
    .borderTop({ width: 1, color: '#e2e8f0' })
    .paddingVertical(12)
    .width('100%');
  }
}

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

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

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

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

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

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

(1)状态管理

React 的 useState 钩子替换为鸿蒙的 @State 装饰器,核心差异在于状态更新方式:

// React Native
const [filterOwned, setFilterOwned] = useState<boolean>(false);
// 更新状态
setFilterOwned(!filterOwned);

// 鸿蒙
@State filterOwned: boolean = false;
// 更新状态
this.filterOwned = !this.filterOwned;

适配要点

  • React 状态更新需要调用 setter 函数,鸿蒙直接赋值即可;
  • React 状态更新是不可变的,鸿蒙直接修改状态对象;
  • 两者的响应式原理不同,但开发体验和效果一致;
  • 鸿蒙支持计算属性(get sortedAmiibos()),简化数据处理逻辑。
(2)横向滚动标签栏

React Native 的横向 ScrollView 替换为鸿蒙的 Scroll 组件 + scrollDirection 属性:

// React Native
<ScrollView 
  horizontal 
  showsHorizontalScrollIndicator={false}
>
  {/* 子元素 */}
</ScrollView>

// 鸿蒙
Scroll({ scrollDirection: ScrollDirection.Horizontal }) {
  {/* 子元素 */}
}
.scrollBar(BarState.Off);

适配优势

  • 鸿蒙的滚动方向通过构造函数参数明确指定,语义更清晰;
  • 滚动条控制通过链式调用实现,代码更简洁;
  • 支持更多滚动特性(弹性效果、滚动速度等)。

该 Amiibo 收藏库应用的跨端适配实践验证了分类列表型应用从 React Native 向鸿蒙迁移的高效性,核心业务逻辑可实现完全复用,仅需适配UI组件层和状态管理语法,这种适配模式特别适合数据驱动的分类列表型应用开发。


真实演示案例代码:






// 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==',
  collect: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  search: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  filter: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  sort: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  settings: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  info: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  more: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};

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

// Amiibo类型
type Amiibo = {
  id: string;
  name: string;
  character: string;
  series: string;
  releaseDate: string;
  price: number;
  owned: boolean;
  rarity: 'common' | 'rare' | 'epic' | 'legendary';
  image: string;
};

// 系列类型
type Series = {
  id: string;
  name: string;
  icon: string;
};

// 模拟数据
const amiiboSeries: Series[] = [
  { id: '1', name: '超级马里奥', icon: '🍄' },
  { id: '2', name: '塞尔达传说', icon: '⚔️' },
  { id: '3', name: '宝可梦', icon: '⚡' },
  { id: '4', name: '动物森友会', icon: '🌳' },
  { id: '5', name: '火焰纹章', icon: '🗡️' },
  { id: '6', name: '大金刚', icon: '🍌' },
  { id: '7', name: '星之卡比', icon: '💫' },
  { id: '8', name: '任天堂明星大乱斗', icon: '🎮' },
];

const amiibos: Amiibo[] = [
  {
    id: '1',
    name: '马里奥',
    character: '马里奥',
    series: '超级马里奥',
    releaseDate: '2014-11-21',
    price: 19.99,
    owned: true,
    rarity: 'common',
    image: ''
  },
  {
    id: '2',
    name: '路易吉',
    character: '路易吉',
    series: '超级马里奥',
    releaseDate: '2014-11-21',
    price: 19.99,
    owned: false,
    rarity: 'common',
    image: ''
  },
  {
    id: '3',
    name: '桃花公主',
    character: '桃花公主',
    series: '超级马里奥',
    releaseDate: '2014-11-21',
    price: 24.99,
    owned: true,
    rarity: 'rare',
    image: ''
  },
  {
    id: '4',
    name: '林克',
    character: '林克',
    series: '塞尔达传说',
    releaseDate: '2014-11-21',
    price: 29.99,
    owned: false,
    rarity: 'epic',
    image: ''
  },
  {
    id: '5',
    name: '塞尔达',
    character: '塞尔达',
    series: '塞尔达传说',
    releaseDate: '2014-11-21',
    price: 29.99,
    owned: true,
    rarity: 'epic',
    image: ''
  },
  {
    id: '6',
    name: '皮卡丘',
    character: '皮卡丘',
    series: '宝可梦',
    releaseDate: '2014-11-21',
    price: 19.99,
    owned: false,
    rarity: 'rare',
    image: ''
  },
  {
    id: '7',
    name: '杰尼龟',
    character: '杰尼龟',
    series: '宝可梦',
    releaseDate: '2014-11-21',
    price: 19.99,
    owned: true,
    rarity: 'common',
    image: ''
  },
  {
    id: '8',
    name: '提米',
    character: '提米',
    series: '动物森友会',
    releaseDate: '2014-11-21',
    price: 14.99,
    owned: true,
    rarity: 'common',
    image: ''
  },
  {
    id: '9',
    name: '罗宾',
    character: '罗宾',
    series: '火焰纹章',
    releaseDate: '2014-11-21',
    price: 24.99,
    owned: false,
    rarity: 'rare',
    image: ''
  },
  {
    id: '10',
    name: '大金刚',
    character: '大金刚',
    series: '大金刚',
    releaseDate: '2014-11-21',
    price: 24.99,
    owned: true,
    rarity: 'epic',
    image: ''
  },
];

const GameLibraryApp: React.FC = () => {
  const [collection, setCollection] = useState<Amiibo[]>(amiibos);
  const [activeSeries, setActiveSeries] = useState<string>('1');
  const [searchQuery, setSearchQuery] = useState<string>('');
  const [sortBy, setSortBy] = useState<'name' | 'date' | 'price'>('name');
  const [filterOwned, setFilterOwned] = useState<boolean>(false);

  // 获取当前系列的Amiibo
  const getCurrentSeriesAmiibos = () => {
    const seriesName = amiiboSeries.find(series => series.id === activeSeries)?.name;
    return collection.filter(amiibo => 
      amiibo.series === seriesName &&
      (searchQuery === '' || amiibo.name.toLowerCase().includes(searchQuery.toLowerCase())) &&
      (!filterOwned || amiibo.owned)
    );
  };

  // 按排序方式整理数据
  const sortedAmiibos = getCurrentSeriesAmiibos().sort((a, b) => {
    switch (sortBy) {
      case 'name':
        return a.name.localeCompare(b.name);
      case 'date':
        return new Date(a.releaseDate).getTime() - new Date(b.releaseDate).getTime();
      case 'price':
        return a.price - b.price;
      default:
        return 0;
    }
  });

  // 切换拥有状态
  const toggleOwned = (id: string) => {
    setCollection(collection.map(amiibo => 
      amiibo.id === id ? { ...amiibo, owned: !amiibo.owned } : amiibo
    ));
  };

  // 获取稀有度颜色
  const getRarityColor = (rarity: string) => {
    switch (rarity) {
      case 'common': return '#9ca3af';
      case 'rare': return '#3b82f6';
      case 'epic': return '#8b5cf6';
      case 'legendary': return '#f59e0b';
      default: return '#9ca3af';
    }
  };

  // 渲染Amiibo项
  const renderAmiiboItem = ({ item }: { item: Amiibo }) => (
    <View style={styles.amiiboItem}>
      <View style={styles.imageContainer}>
        <View style={styles.imagePlaceholder}>
          <Text style={styles.imageText}> toy </Text>
        </View>
      </View>
      
      <View style={styles.infoContainer}>
        <View style={styles.headerContainer}>
          <Text style={styles.name}>{item.name}</Text>
          <TouchableOpacity 
            style={styles.ownedButton} 
            onPress={() => toggleOwned(item.id)}
          >
            <Text style={styles.ownedText}>{item.owned ? '✅' : '⬜'}</Text>
          </TouchableOpacity>
        </View>
        
        <Text style={styles.character}>{item.character}</Text>
        <Text style={styles.series}>{item.series}</Text>
        
        <View style={styles.detailsContainer}>
          <Text style={styles.releaseDate}>发行: {item.releaseDate}</Text>
          <Text style={styles.price}>¥{item.price.toFixed(2)}</Text>
        </View>
        
        <View style={styles.rarityContainer}>
          <Text style={[styles.rarity, { color: getRarityColor(item.rarity) }]}>
            {item.rarity === 'common' ? '普通' : item.rarity === 'rare' ? '稀有' : item.rarity === 'epic' ? '史诗' : '传说'}
          </Text>
        </View>
      </View>
    </View>
  );

  return (
    <SafeAreaView style={styles.container}>
      {/* 头部 */}
      <View style={styles.header}>
        <Text style={styles.title}>Amiibo收藏库</Text>
        <View style={styles.headerActions}>
          <TouchableOpacity style={styles.searchButton} onPress={() => Alert.alert('搜索')}>
            <Text style={styles.searchIcon}>🔍</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.settingsButton} onPress={() => Alert.alert('设置')}>
            <Text style={styles.settingsIcon}>⚙️</Text>
          </TouchableOpacity>
        </View>
      </View>

      <ScrollView style={styles.content}>
        {/* 搜索栏 */}
        <View style={styles.searchContainer}>
          <Text style={styles.searchIcon}>🔍</Text>
          <Text style={styles.searchPlaceholder}>搜索Amiibo名称</Text>
        </View>

        {/* 系列分类 */}
        <Text style={styles.sectionTitle}>Amiibo系列</Text>
        <ScrollView 
          horizontal 
          showsHorizontalScrollIndicator={false} 
          style={styles.seriesContainer}
        >
          <View style={styles.seriesList}>
            {amiiboSeries.map(series => (
              <TouchableOpacity
                key={series.id}
                style={[
                  styles.seriesItem,
                  activeSeries === series.id && styles.activeSeries
                ]}
                onPress={() => setActiveSeries(series.id)}
              >
                <Text style={[
                  styles.seriesIcon,
                  activeSeries === series.id && styles.activeSeriesIcon
                ]}>
                  {series.icon}
                </Text>
                <Text style={[
                  styles.seriesText,
                  activeSeries === series.id && styles.activeSeriesText
                ]}>
                  {series.name}
                </Text>
              </TouchableOpacity>
            ))}
          </View>
        </ScrollView>

        {/* 筛选和排序选项 */}
        <View style={styles.filtersContainer}>
          <TouchableOpacity 
            style={[styles.filterButton, filterOwned && styles.activeFilter]}
            onPress={() => setFilterOwned(!filterOwned)}
          >
            <Text style={styles.filterText}>只看拥有</Text>
          </TouchableOpacity>
          
          <View style={styles.sortContainer}>
            <Text style={styles.sortLabel}>排序:</Text>
            <TouchableOpacity 
              style={[styles.sortButton, sortBy === 'name' && styles.activeSort]}
              onPress={() => setSortBy('name')}
            >
              <Text style={styles.sortText}>名称</Text>
            </TouchableOpacity>
            <TouchableOpacity 
              style={[styles.sortButton, sortBy === 'date' && styles.activeSort]}
              onPress={() => setSortBy('date')}
            >
              <Text style={styles.sortText}>日期</Text>
            </TouchableOpacity>
            <TouchableOpacity 
              style={[styles.sortButton, sortBy === 'price' && styles.activeSort]}
              onPress={() => setSortBy('price')}
            >
              <Text style={styles.sortText}>价格</Text>
            </TouchableOpacity>
          </View>
        </View>

        {/* 收藏统计 */}
        <View style={styles.statsContainer}>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>{sortedAmiibos.length}</Text>
            <Text style={styles.statLabel}>当前系列</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>{collection.filter(a => a.owned).length}</Text>
            <Text style={styles.statLabel}>已拥有</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>{collection.filter(a => !a.owned).length}</Text>
            <Text style={styles.statLabel}>未拥有</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>10</Text>
            <Text style={styles.statLabel}>总计</Text>
          </View>
        </View>

        {/* Amiibo列表 */}
        <Text style={styles.sectionTitle}>
          {amiiboSeries.find(s => s.id === activeSeries)?.name || 'Amiibo'} ({sortedAmiibos.length})
        </Text>
        
        <FlatList
          data={sortedAmiibos}
          renderItem={renderAmiiboItem}
          keyExtractor={item => item.id}
          showsVerticalScrollIndicator={false}
        />

        {/* 收藏说明 */}
        <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',
  },
  headerActions: {
    flexDirection: 'row',
  },
  searchButton: {
    width: 36,
    height: 36,
    borderRadius: 18,
    backgroundColor: '#f1f5f9',
    alignItems: 'center',
    justifyContent: 'center',
    marginRight: 12,
  },
  searchIcon: {
    fontSize: 18,
    color: '#64748b',
  },
  settingsButton: {
    width: 36,
    height: 36,
    borderRadius: 18,
    backgroundColor: '#f1f5f9',
    alignItems: 'center',
    justifyContent: 'center',
  },
  settingsIcon: {
    fontSize: 18,
    color: '#64748b',
  },
  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,
  },
  searchPlaceholder: {
    fontSize: 14,
    color: '#94a3b8',
    marginLeft: 12,
    flex: 1,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#1e293b',
    marginVertical: 12,
  },
  seriesContainer: {
    marginBottom: 16,
  },
  seriesList: {
    flexDirection: 'row',
  },
  seriesItem: {
    backgroundColor: '#ffffff',
    borderRadius: 20,
    paddingVertical: 8,
    paddingHorizontal: 16,
    marginRight: 12,
    alignItems: 'center',
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  activeSeries: {
    backgroundColor: '#3b82f6',
  },
  seriesIcon: {
    fontSize: 16,
    marginBottom: 4,
    color: '#64748b',
  },
  activeSeriesIcon: {
    color: '#ffffff',
  },
  seriesText: {
    fontSize: 12,
    color: '#64748b',
  },
  activeSeriesText: {
    color: '#ffffff',
    fontWeight: '500',
  },
  filtersContainer: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 12,
    marginBottom: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  filterButton: {
    backgroundColor: '#f1f5f9',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 20,
    alignSelf: 'flex-start',
    marginBottom: 12,
  },
  activeFilter: {
    backgroundColor: '#3b82f6',
  },
  filterText: {
    fontSize: 12,
    color: '#3b82f6',
    fontWeight: '500',
  },
  activeFilterText: {
    color: '#ffffff',
  },
  sortContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  sortLabel: {
    fontSize: 14,
    color: '#64748b',
    marginRight: 8,
  },
  sortButton: {
    backgroundColor: '#f1f5f9',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 20,
    marginLeft: 8,
  },
  activeSort: {
    backgroundColor: '#3b82f6',
  },
  sortText: {
    fontSize: 12,
    color: '#3b82f6',
  },
  activeSortText: {
    color: '#ffffff',
  },
  statsContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginBottom: 16,
  },
  statItem: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 12,
    alignItems: 'center',
    flex: 1,
    marginHorizontal: 4,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  statNumber: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#3b82f6',
  },
  statLabel: {
    fontSize: 12,
    color: '#64748b',
    marginTop: 4,
  },
  amiiboItem: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    flexDirection: 'row',
    padding: 12,
    marginBottom: 12,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  imageContainer: {
    marginRight: 12,
  },
  imagePlaceholder: {
    width: 80,
    height: 80,
    backgroundColor: '#e2e8f0',
    borderRadius: 8,
    alignItems: 'center',
    justifyContent: 'center',
  },
  imageText: {
    fontSize: 24,
  },
  infoContainer: {
    flex: 1,
    justifyContent: 'space-between',
  },
  headerContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 4,
  },
  name: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    flex: 1,
  },
  ownedButton: {
    marginLeft: 8,
  },
  ownedText: {
    fontSize: 18,
  },
  character: {
    fontSize: 14,
    color: '#3b82f6',
    marginBottom: 4,
  },
  series: {
    fontSize: 12,
    color: '#64748b',
    marginBottom: 8,
  },
  detailsContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 4,
  },
  releaseDate: {
    fontSize: 12,
    color: '#94a3b8',
  },
  price: {
    fontSize: 14,
    fontWeight: 'bold',
    color: '#10b981',
  },
  rarityContainer: {
    alignSelf: 'flex-start',
  },
  rarity: {
    fontSize: 12,
    fontWeight: '500',
  },
  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 GameLibraryApp;


请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述

本文分析了基于React Native实现的游戏收藏应用架构设计。应用采用组件化结构,包含主应用组件、列表渲染、系列筛选、搜索排序等功能模块,实现清晰的关注点分离。状态管理使用useState钩子处理收藏数据、当前系列、搜索查询等核心状态,结合TypeScript确保类型安全。应用实现了多重筛选(系列、搜索关键字、拥有状态)和排序(名称、日期、价格)功能,通过颜色区分不同稀有度。布局采用顶部标题、系列筛选、功能栏和列表的层次结构,提供直观交互体验。优化建议包括使用FlatList性能参数和useReducer管理复杂状态。该实现展示了React Native在收藏类应用开发中的完整解决方案。

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

Logo

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

更多推荐