RN for OpenHarmony英雄联盟助手App实战:装备大全实现
本文介绍了英雄联盟装备列表页的实现方案,重点包括: 数据结构特点:200+件装备,多种分类方式(攻击/防御/法术等),包含合成关系和价格属性。 页面架构: 使用ItemGrid组件展示装备网格 通过TabBar实现可横向滚动的分类标签栏 结合SearchBar实现搜索筛选功能 性能优化: 采用数据缓存策略,避免重复请求 使用useMemo优化筛选逻辑 支持主题切换的动态样式 交互设计: 分类筛选与

案例开源地址:https://atomgit.com/nutpi/rn_openharmony_lol
英雄联盟有 200 多件装备,从基础的长剑、布甲,到成型的无尽之刃、兰顿之兆。装备大全页要把这些装备分类展示,并支持搜索和筛选,让用户能快速找到想要的装备。
这篇文章我们来实现装备列表页,重点是分类标签栏的设计、搜索和筛选的组合使用、以及数据缓存策略。
装备数据的特点
和英雄数据不同,装备数据有一些特殊之处:
- 数量更多:200+ 件装备,比英雄还多
- 分类复杂:装备有多种分类方式,按属性(攻击、防御、法术等)、按类型(基础装备、成型装备)
- 合成关系:装备之间有合成关系,A + B = C
- 价格属性:每件装备都有价格,影响出装顺序
我们先实现基础的列表展示和分类筛选,合成关系在后面的文章中单独讲。
页面结构
import React, {useEffect, useState, useMemo} from 'react';
import {View, StyleSheet} from 'react-native';
import {useTheme} from '../../context/ThemeContext';
import {useApp} from '../../context/AppContext';
import {useNavigation} from '../../context/NavigationContext';
import {itemApi} from '../../api';
import {ItemGrid} from '../../components/item';
import {SearchBar, TabBar, Loading} from '../../components/common';
import {useDebounce} from '../../hooks';
import type {Item} from '../../models/Item';
这个页面用到了几个新组件:
ItemGrid:装备网格组件,类似 ChampionGridTabBar:分类标签栏,支持横向滚动
export function ItemListPage() {
const {colors} = useTheme();
const {state, setItems} = useApp();
const {navigate} = useNavigation();
const [loading, setLoading] = useState(true);
const [searchText, setSearchText] = useState('');
const [activeTag, setActiveTag] = useState('All');
const debouncedSearch = useDebounce(searchText, 300);
const categories = itemApi.getItemCategories();
状态比英雄列表页多了一个 activeTag,用于记录当前选中的分类。categories 是装备分类列表,从 API 模块获取。
数据加载与缓存
useEffect(() => {
async function loadItems() {
if (!state.version) return;
if (state.items.length === 0) {
setLoading(true);
const items = await itemApi.getItemList(state.version);
setItems(items);
}
setLoading(false);
}
loadItems();
}, [state.version]);
这里有一个缓存策略:if (state.items.length === 0) 判断全局状态中是否已经有装备数据。如果有,就不再请求,直接使用缓存的数据。
为什么要这样做?因为装备数据量大(200+ 条),每次进入页面都请求会浪费流量和时间。装备数据在一个游戏版本内不会变化,缓存是安全的。
数据存储在 AppContext 的全局状态中,通过 setItems 方法更新。这样其他页面(比如装备详情页)也可以直接使用这些数据,不需要重复请求。
筛选逻辑
const filteredItems = useMemo(() => {
let result = state.items;
if (activeTag !== 'All') {
result = itemApi.filterItemsByTag(result, activeTag);
}
if (debouncedSearch) {
result = itemApi.searchItems(result, debouncedSearch);
}
return result;
}, [state.items, activeTag, debouncedSearch]);
筛选逻辑和英雄筛选类似,但这里是分类筛选和搜索的组合。两个条件是"与"的关系:先按分类筛选,再在结果中搜索。
比如用户选了"攻击"分类,然后搜索"剑",会显示所有攻击类装备中名称包含"剑"的装备。
itemApi.filterItemsByTag 和 itemApi.searchItems 是封装的工具函数:
// api/item.ts
export function filterItemsByTag(items: Item[], tag: string): Item[] {
return items.filter(item => item.tags.includes(tag));
}
export function searchItems(items: Item[], keyword: string): Item[] {
const lower = keyword.toLowerCase();
return items.filter(item =>
item.name.toLowerCase().includes(lower) ||
item.description.toLowerCase().includes(lower)
);
}
分类标签栏
装备分类比较多,一行放不下,需要支持横向滚动。
const categories = itemApi.getItemCategories();
// 返回类似这样的数组:
// [
// {key: 'All', label: '全部'},
// {key: 'Damage', label: '攻击'},
// {key: 'Defense', label: '防御'},
// {key: 'SpellDamage', label: '法术'},
// {key: 'AttackSpeed', label: '攻速'},
// {key: 'LifeSteal', label: '吸血'},
// ...
// ]
TabBar 组件接收这个数组,渲染成可滚动的标签栏:
<TabBar
tabs={categories}
activeKey={activeTag}
onTabChange={setActiveTag}
scrollable
/>
scrollable 属性启用横向滚动。当标签总宽度超过屏幕宽度时,用户可以左右滑动查看更多标签。
动态样式
const styles = useMemo(() => StyleSheet.create({
container: {flex: 1, backgroundColor: colors.background},
header: {marginBottom: 8},
tabContainer: {marginTop: 12},
}), [colors]);
样式用 useMemo 包裹,依赖 colors。当主题切换时,colors 变化,样式会重新生成。这是支持深色/浅色模式切换的关键。
为什么不直接写成静态样式?因为 colors.background 是动态的,在深色模式下是深色,浅色模式下是浅色。如果写成静态样式,颜色就固定了,无法响应主题切换。
页面渲染
const handleItemPress = (item: Item) => {
navigate('ItemDetail', {itemId: item.id});
};
if (loading) return <Loading fullScreen />;
const ListHeader = (
<View style={styles.header}>
<SearchBar
value={searchText}
onChangeText={setSearchText}
placeholder="搜索装备名称..."
/>
<View style={styles.tabContainer}>
<TabBar
tabs={categories}
activeKey={activeTag}
onTabChange={setActiveTag}
scrollable
/>
</View>
</View>
);
return (
<View style={styles.container}>
<ItemGrid
items={filteredItems}
version={state.version}
onItemPress={handleItemPress}
ListHeaderComponent={ListHeader}
/>
</View>
);
}
ListHeader 包含搜索框和分类标签栏,放在 ItemGrid 的顶部。搜索框在上,标签栏在下,这是常见的布局方式。
TabBar 组件的实现
TabBar 是一个通用的标签栏组件,支持固定宽度和可滚动两种模式。
interface TabBarProps {
tabs: Array<{key: string; label: string}>;
activeKey: string;
onTabChange: (key: string) => void;
scrollable?: boolean;
}
export function TabBar({tabs, activeKey, onTabChange, scrollable}: TabBarProps) {
const Container = scrollable ? ScrollView : View;
const containerProps = scrollable ? {
horizontal: true,
showsHorizontalScrollIndicator: false,
} : {};
return (
<Container style={styles.container} {...containerProps}>
{tabs.map(tab => (
<TouchableOpacity
key={tab.key}
style={[styles.tab, activeKey === tab.key && styles.tabActive]}
onPress={() => onTabChange(tab.key)}>
<Text style={[styles.tabText, activeKey === tab.key && styles.tabTextActive]}>
{tab.label}
</Text>
</TouchableOpacity>
))}
</Container>
);
}
scrollable 属性决定使用 ScrollView 还是普通 View 作为容器。ScrollView 设置 horizontal: true 启用横向滚动。
这种设计让组件更灵活:标签少的时候用固定布局,标签多的时候用滚动布局,同一个组件都能处理。
装备数据结构
装备的数据结构比英雄简单一些:
interface Item {
id: string;
name: string;
description: string;
plaintext: string; // 简短描述
gold: {
base: number; // 基础价格
total: number; // 总价格
sell: number; // 出售价格
};
tags: string[]; // 分类标签
stats: Record<string, number>; // 属性加成
from?: string[]; // 合成所需的装备 ID
into?: string[]; // 可以合成的装备 ID
image: {
full: string;
};
}
from 和 into 字段描述了装备的合成关系,我们会在"合成树"那篇文章中详细讲解。
ItemGrid 组件
ItemGrid 和 ChampionGrid 类似,用 FlatList 实现网格布局:
export function ItemGrid({items, version, onItemPress, ListHeaderComponent}) {
return (
<FlatList
data={items}
keyExtractor={item => item.id}
numColumns={4}
contentContainerStyle={styles.list}
ListHeaderComponent={ListHeaderComponent}
renderItem={({item}) => (
<ItemCard
item={item}
version={version}
onPress={() => onItemPress(item)}
/>
)}
/>
);
}
装备图标比英雄头像小,所以用 4 列布局(英雄用 3 列)。这样一屏能显示更多装备,方便用户浏览。
性能优化
装备列表有 200+ 条数据,需要注意性能:
FlatList 的虚拟化:FlatList 默认只渲染可视区域的元素,滚动时动态加载。这是处理长列表的标准方案。
图片缓存:装备图标会被 React Native 自动缓存,同一张图片只下载一次。
useMemo 优化:筛选逻辑用 useMemo 包裹,只有依赖项变化时才重新计算。
防抖搜索:搜索输入用 useDebounce 防抖,避免频繁触发筛选。
可能的优化方向
价格排序:加一个排序选项,让用户可以按价格从低到高或从高到低排序。对于想看便宜装备或贵装备的用户很有用。
属性筛选:除了分类筛选,还可以按具体属性筛选,比如"加攻击力的装备"、“加暴击的装备”。
收藏功能:让用户可以收藏常用的装备,方便快速查看。
出装推荐:根据用户选择的英雄,推荐适合的装备。这需要额外的数据支持。
小结
装备大全页在英雄列表页的基础上增加了几个功能:
- 分类标签栏:用 TabBar 组件实现可滚动的分类筛选
- 搜索 + 筛选组合:两个条件同时生效,缩小结果范围
- 数据缓存:装备数据存储在全局状态中,避免重复请求
- 动态样式:用 useMemo 生成样式,支持主题切换
下一篇我们来实现装备详情页,展示装备的属性、价格和合成信息。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)