rn_for_openharmony_steam资讯app实战-即将推出实现
本文介绍了如何使用React Native开发OpenHarmony平台的Steam资讯App中的"即将推出"页面。文章区分了"新品上架"和"即将推出"两个功能的差异,重点讲解了价格显示的特殊处理(未定价游戏显示"即将发售")、预购游戏的处理逻辑以及代码实现细节。作者还回顾了featuredcategories接口系列
React Native for OpenHarmony 实战:Steam 资讯 App 即将推出页面
案例开源地址:https://atomgit.com/nutpi/rn_openharmony_steam
作为一个老玩家,我经常会关注 Steam 上即将发售的游戏。有时候看到感兴趣的新作,提前加个愿望单,等发售了第一时间入手。这篇文章来实现"即将推出"页面,也是 featuredcategories 接口系列的收官之作。
即将推出 vs 新品上架
这两个页面容易混淆,先理清一下:
- 新品上架(new_releases):已经发售的新游戏,可以直接购买
- 即将推出(coming_soon):还没发售的游戏,只能加愿望单
从用户心理来说,看新品是"买买买"的冲动,看即将推出是"期待期待"的心情。虽然 UI 结构相似,但业务含义不同。
价格显示的特殊处理
即将推出的游戏有个特点:很多还没定价。
看一下 API 返回的数据:
{
"id": 123456,
"name": "Some Upcoming Game",
"discounted": false,
"discount_percent": 0,
"original_price": null,
"final_price": null,
"header_image": "https://cdn.xxx/xxx.jpg"
}
注意 final_price 是 null,不是 0。这和免费游戏不一样——免费游戏的 final_price 是 0,而未定价游戏是 null。
所以价格处理逻辑要调整:
price={game.final_price ? `¥${(game.final_price / 100).toFixed(2)}` : '即将发售'}
这里把默认文案从"免费"改成了"即将发售",更符合业务语义。
当然,有些即将推出的游戏已经定价了(可以预购),这时候 final_price 有值,就正常显示价格。
代码实现
直接看完整代码:
import React, {useEffect, useState} from 'react';
import {View, ScrollView, StyleSheet} from 'react-native';
import {Header} from '../components/Header';
import {TabBar} from '../components/TabBar';
import {GameCard} from '../components/GameCard';
import {Loading} from '../components/Loading';
import {getFeaturedCategories} from '../api/steam';
export const UpcomingScreen = () => {
const [games, setGames] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
getFeaturedCategories().then(data => {
setGames(data?.coming_soon?.items || []);
setLoading(false);
}).catch(() => setLoading(false));
}, []);
return (
<View style={styles.container}>
<Header title="即将推出" showBack />
{loading ? <Loading /> : (
<ScrollView style={styles.content}>
{games.map((game: any) => (
<GameCard
key={game.id}
appId={game.id}
name={game.name}
image={game.header_image}
price={game.final_price ? `¥${(game.final_price / 100).toFixed(2)}` : '即将发售'}
/>
))}
</ScrollView>
)}
<TabBar />
</View>
);
};
const styles = StyleSheet.create({
container: {flex: 1, backgroundColor: '#171a21'},
content: {flex: 1, padding: 16},
});
和前几个页面对比,主要区别就两点:
- 数据路径是
data?.coming_soon?.items - 价格默认文案是"即将发售"而不是"免费"
为什么不传 discount 属性
你可能注意到了,这个页面的 GameCard 没有传 discount 属性:
<GameCard
key={game.id}
appId={game.id}
name={game.name}
image={game.header_image}
price={game.final_price ? `¥${(game.final_price / 100).toFixed(2)}` : '即将发售'}
// 没有 discount={game.discount_percent}
/>
原因很简单:即将推出的游戏基本不会有折扣。它们还没发售呢,打什么折?
虽然 API 返回的数据里有 discount_percent 字段(值是 0),但传不传都一样。不传的话代码更简洁,也更能体现业务意图。
写代码不只是让程序跑起来,还要让读代码的人(包括未来的自己)能快速理解意图。
关于预购
Steam 上有些大作会开放预购,比如 3A 游戏在发售前几个月就能预购了。预购的游戏:
final_price有值(预购价格)- 可能有预购折扣(
discount_percent> 0) - 点击后跳转到详情页可以看到预购按钮
我们当前的实现已经能正确处理预购游戏:有价格就显示价格,有折扣就显示折扣标签。不需要额外改动。
点击卡片后的行为
用户点击即将推出的游戏卡片,会跳转到游戏详情页。这个逻辑在 GameCard 组件里:
const onPress = () => {
setSelectedAppId(appId);
addToHistory(appId);
navigate('gameDetail');
};
详情页会调用 appdetails 接口获取完整信息,包括发售日期、游戏介绍、截图视频等。即将推出的游戏和已发售的游戏用的是同一个详情页,只是展示的内容略有不同(比如购买按钮变成愿望单按钮)。
这种设计的好处是复用。不需要为即将推出的游戏单独做一个详情页,减少了代码量和维护成本。
featuredcategories 系列回顾
到这里,featuredcategories 接口的四个分类都实现完了:
featuredcategories
├── specials → 特惠游戏(第 03 篇)
├── top_sellers → 热销榜(第 04 篇)
├── new_releases → 新品上架(第 05 篇)
└── coming_soon → 即将推出(本篇)
四个页面的代码结构几乎一样,区别只在于:
- 数据提取路径不同
- Header 标题不同
- 价格默认文案不同(热销榜还多了排名显示)
这种"相似但有细微差异"的场景在实际开发中很常见。处理方式有两种:
- 保持独立:每个页面单独写,代码有重复但清晰
- 抽象复用:提取通用组件,通过参数控制差异
我们选择了方案一,因为代码量不大,而且各页面可能有不同的演进方向。如果你的项目有更多类似页面,方案二会更合适。
发售日期的展示思考
即将推出的游戏,用户最关心的信息之一就是发售日期。可惜 featuredcategories 接口返回的数据里没有这个字段。
如果想展示发售日期,需要额外调用 appdetails 接口:
export const getAppDetails = async (appId: number) => {
const res = await fetch(`${STORE_API}/appdetails?appids=${appId}`);
return res.json();
};
返回数据中有个 release_date 字段:
{
"release_date": {
"coming_soon": true,
"date": "2024年第四季度"
}
}
但这样做有个问题:列表里有多少游戏,就要调多少次接口。如果列表有 20 个游戏,就是 20 次请求,性能和体验都不好。
比较好的做法是:
- 列表页不显示发售日期,保持简洁
- 用户点进详情页后再展示完整信息
这也是我们当前的实现方式。
空状态处理
如果 API 返回的 coming_soon.items 是空数组怎么办?当前的实现会显示一个空白页面,用户体验不太好。
可以加个空状态提示:
{games.length === 0 ? (
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>暂无即将推出的游戏</Text>
</View>
) : (
<ScrollView style={styles.content}>
{games.map((game: any) => (
// ...
))}
</ScrollView>
)}
不过实际上 Steam 的即将推出列表基本不会为空,总有新游戏在排队等发售。所以这个优化的优先级不高,但作为一个完善的 App,空状态处理是应该考虑的。
加载失败的处理
当前的错误处理比较简单:
.catch(() => setLoading(false));
请求失败时只是把 loading 设为 false,用户看到的是空列表,不知道发生了什么。
更友好的做法是加个错误状态:
const [error, setError] = useState(false);
useEffect(() => {
getFeaturedCategories()
.then(data => {
setGames(data?.coming_soon?.items || []);
})
.catch(() => {
setError(true);
})
.finally(() => {
setLoading(false);
});
}, []);
然后在渲染时判断:
{error ? (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>加载失败</Text>
<TouchableOpacity onPress={reload}>
<Text style={styles.retryText}>点击重试</Text>
</TouchableOpacity>
</View>
) : (
// 正常渲染
)}
这个优化在前面的文章也提过,这里再强调一下。生产环境的 App 一定要做好错误处理,不能让用户面对一个空白页面不知所措。
首页快捷入口的完善
回顾一下首页的快捷入口配置:
{[
{name: 'featured', label: '精选', icon: '⭐'},
{name: 'specials', label: '特惠', icon: '🏷️'},
{name: 'topSellers', label: '热销', icon: '🔥'},
{name: 'newReleases', label: '新品', icon: '🆕'},
].map(item => (
<TouchableOpacity key={item.name} onPress={() => navigate(item.name)}>
...
</TouchableOpacity>
))}
你会发现这里没有"即将推出"的入口。这是故意的——首页空间有限,只放最常用的四个入口。
如果想加上"即将推出",有几种方案:
- 把快捷入口从 4 个扩展到 5 个(可能会显得拥挤)
- 做成两行的宫格布局
- 放到"更多"菜单里
这些都是产品设计的选择,技术上都能实现。
页面跳转的路由配置
即将推出页面的路由名是 upcoming,在 App.tsx 的路由配置中:
const screens: Record<string, React.ReactNode> = {
// 首页相关
home: <HomeScreen />,
featured: <FeaturedScreen />,
specials: <SpecialsScreen />,
topSellers: <TopSellersScreen />,
newReleases: <NewReleasesScreen />,
upcoming: <UpcomingScreen />,
// ...其他页面
};
用户可以通过以下方式进入这个页面:
- 首页某个入口点击(如果配置了的话)
- 其他页面的链接跳转
- 深度链接(如果实现了的话)
我们的导航系统用的是简单的状态管理,通过 navigate('upcoming') 就能跳转。如果项目规模更大,可以考虑用 React Navigation 这样的专业导航库。
小结
即将推出页面的实现很简单,但背后有一些值得思考的点:
- 业务语义:同样是"没有价格",免费游戏和未定价游戏的含义不同,显示文案也应该不同
- 代码意图:不传
discount属性不是忘了,而是业务上不需要 - 复用思维:详情页不区分已发售和即将推出,减少重复代码
下一篇开始进入游戏详情相关的页面开发,内容会丰富很多。详情页需要展示游戏介绍、截图、视频、成就、新闻等信息,涉及多个 API 的调用和复杂的 UI 布局,敬请期待。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)