WanAndroid API 鸿蒙化适配:首页文章列表 FlatList
本文介绍了在React Native中使用FlatList组件构建WanAndroid应用首页列表的关键实现。FlatList通过虚拟化技术优化性能,只渲染可见区域元素。文章详细解析了数据结构、状态管理、数据加载逻辑(包括刷新和加载更多),以及如何合并置顶与普通文章。重点展示了FlatList的核心配置项,包括数据源、key生成、列表项渲染、头部组件、下拉刷新和上拉加载的实现。通过合理的组件设计和

案例项目开源地址:https://atomgit.com/nutpi/wanandroid_rn_openharmony
列表是应用的骨架
打开任何一个资讯类 App,首页必然是一个列表。微信的聊天列表、微博的动态流、知乎的推荐页,都是列表。列表承载了应用最核心的内容展示功能。
我们的 WanAndroid 技术资讯应用也不例外。首页需要展示从 wanandroid.com 获取的技术文章列表,用户可以滚动浏览、下拉刷新、上拉加载更多。这一切的基础,就是 FlatList 组件。
为什么选择 FlatList
React Native 提供了几种列表组件:ScrollView、FlatList、SectionList。
ScrollView 最简单,但它会一次性渲染所有子元素。如果列表有 100 条数据,就会渲染 100 个组件。数据量大的时候,内存占用高,滚动卡顿。
FlatList 采用了"虚拟化"技术。它只渲染当前可见区域的元素,滚动时动态回收和复用。100 条数据,可能只渲染 10 个组件。内存占用低,滚动流畅。
对于文章列表这种数据量不确定、可能很多的场景,FlatList 是最佳选择。
首页的数据结构
先看看我们要展示什么数据。wanandroid 的首页文章接口返回的数据结构大概是这样:
interface Article {
id: number;
title: string;
author: string;
shareUser: string;
niceDate: string;
link: string;
superChapterName: string;
chapterName: string;
collect: boolean;
fresh: boolean;
envelopePic: string;
desc: string;
}
每篇文章有标题、作者、日期、链接、分类等信息。fresh 表示是否是新文章,collect 表示是否已收藏。
首页组件的状态管理
首页需要管理几个状态:文章列表、置顶文章、Banner、当前页码、加载状态、刷新状态。
export const HomePage = () => {
const {theme} = useTheme();
const [articles, setArticles] = useState<Article[]>([]);
const [topArticles, setTopArticles] = useState<Article[]>([]);
const [banners, setBanners] = useState<Banner[]>([]);
const [page, setPage] = useState(0);
const [loading, setLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
useEffect(() => {
loadData(true);
}, []);
page 从 0 开始,这是 wanandroid API 的约定。loading 用于上拉加载更多时显示加载指示器,refreshing 用于下拉刷新时显示刷新动画。
组件挂载时调用 loadData(true),true 表示这是一次刷新操作,需要重置页码并重新加载所有数据。
加载数据的逻辑
const loadData = async (refresh = false) => {
if (refresh) {
setRefreshing(true);
setPage(0);
} else {
setLoading(true);
}
try {
if (refresh) {
const [bannerRes, topRes, listRes] = await Promise.all([
homeApi.getBanners(),
homeApi.getTopArticles(),
homeApi.getArticles(0),
]);
if (bannerRes.errorCode === 0) setBanners(bannerRes.data);
if (topRes.errorCode === 0) setTopArticles(topRes.data);
if (listRes.errorCode === 0) setArticles(listRes.data.datas);
}
} catch (e) {
console.error('加载首页失败', e);
}
setLoading(false);
setRefreshing(false);
};
刷新时,用 Promise.all 并行请求三个接口:Banner、置顶文章、普通文章列表。并行请求比串行快,用户等待时间更短。
每个接口返回的数据都有 errorCode 字段,0 表示成功。这是 wanandroid API 的统一约定。
加载更多的逻辑
const loadMore = async () => {
if (loading) return;
setLoading(true);
const nextPage = page + 1;
try {
const res = await homeApi.getArticles(nextPage);
if (res.errorCode === 0) {
setArticles([...articles, ...res.data.datas]);
setPage(nextPage);
}
} catch (e) {}
setLoading(false);
};
加载更多时,先检查是否正在加载,避免重复请求。然后请求下一页数据,成功后追加到现有列表末尾。
注意 setArticles([...articles, ...res.data.datas]) 这种写法,用展开运算符创建新数组,而不是直接 push。这是 React 状态更新的正确姿势,直接修改原数组不会触发重新渲染。
合并置顶文章和普通文章
const allArticles = [
...topArticles.map(a => ({...a, isTop: true})),
...articles,
];
置顶文章要显示在列表最前面,所以把它们放在数组开头。给置顶文章加一个 isTop: true 标记,渲染时可以显示"置顶"标签。
FlatList 的核心配置
return (
<FlatList
data={allArticles}
keyExtractor={(item, index) => `${item.id}-${index}`}
renderItem={({item}) => (
<View>
{(item as any).isTop && (
<View style={[styles.topTag, {backgroundColor: theme.danger}]}>
<Text style={styles.topText}>置顶</Text>
</View>
)}
<ArticleCard item={item} />
</View>
)}
ListHeaderComponent={<BannerSection banners={banners} />}
contentContainerStyle={styles.list}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={() => loadData(true)} tintColor={theme.accent} />
}
onEndReached={loadMore}
onEndReachedThreshold={0.3}
ListFooterComponent={loading ? <ActivityIndicator color={theme.accent} style={{padding: 20}} /> : null}
ListEmptyComponent={
!loading ? (
<View style={styles.empty}>
<Text style={styles.emptyIcon}>📰</Text>
<Text style={[styles.emptyText, {color: theme.subText}]}>暂无文章</Text>
</View>
) : null
}
/>
);
逐个解释这些属性:
data
列表的数据源,是一个数组。FlatList 会遍历这个数组,为每个元素调用 renderItem。
keyExtractor
为每个列表项生成唯一的 key。React 用 key 来追踪元素的变化,优化渲染性能。
这里用 ${item.id}-${index} 而不是单独用 item.id,是因为置顶文章和普通文章可能有相同的 id(置顶文章也会出现在普通列表里),加上 index 可以避免 key 重复。
renderItem
渲染每个列表项的函数。接收一个对象参数,包含 item(当前数据)、index(索引)等。
我们检查 isTop 标记,如果是置顶文章就显示一个红色的"置顶"标签,然后渲染 ArticleCard 组件。
ListHeaderComponent
列表头部组件,显示在所有列表项之前。我们把 Banner 轮播图放在这里。
refreshControl
下拉刷新控件。RefreshControl 是 React Native 内置组件,refreshing 控制刷新动画的显示,onRefresh 是触发刷新时的回调。
tintColor 设置刷新指示器的颜色,我们用主题的强调色。
onEndReached 和 onEndReachedThreshold
onEndReached 是滚动到列表底部时的回调,用于触发加载更多。
onEndReachedThreshold 是触发阈值,0.3 表示距离底部还有 30% 的距离时就触发。设置一个提前量,用户滚动时数据就开始加载了,体验更流畅。
ListFooterComponent
列表底部组件。加载更多时显示一个 ActivityIndicator 转圈动画,告诉用户正在加载。
ListEmptyComponent
列表为空时显示的组件。注意要判断 !loading,否则首次加载时也会显示空状态。
样式定义
const styles = StyleSheet.create({
list: {paddingBottom: 100},
topTag: {paddingHorizontal: 8, paddingVertical: 2, borderRadius: 4, alignSelf: 'flex-start', marginBottom: 4},
topText: {color: '#fff', fontSize: 10, fontWeight: 'bold'},
empty: {alignItems: 'center', paddingVertical: 60},
emptyIcon: {fontSize: 64, marginBottom: 16},
emptyText: {fontSize: 16},
});
list 的 paddingBottom: 100 是为了给底部 Tab 栏留出空间,避免最后一条数据被遮挡。
topTag 用 alignSelf: 'flex-start' 让标签宽度自适应内容,而不是撑满整行。
API 封装
首页用到的 API 封装在 src/services/api.ts 里:
export const homeApi = {
getBanners: () => api.get('/banner/json'),
getTopArticles: () => api.get('/article/top/json'),
getArticles: (page: number) => api.get(`/article/list/${page}/json`),
};
三个接口:
/banner/json获取 Banner 数据/article/top/json获取置顶文章/article/list/{page}/json获取文章列表,page 从 0 开始
把 API 调用封装成函数,调用方不需要关心具体的 URL 和请求方式,代码更清晰。
性能优化建议
FlatList 还有一些性能优化的属性,虽然我们的代码里没有用到,但值得了解:
initialNumToRender
首次渲染的元素数量,默认是 10。如果首屏能显示 5 条,可以设置成 5,减少首次渲染的工作量。
maxToRenderPerBatch
每次渲染的最大元素数量,默认是 10。数值越小,渲染越平滑,但滚动快时可能出现空白。
windowSize
渲染窗口的大小,默认是 21(可见区域的 21 倍)。数值越大,滚动时空白越少,但内存占用越高。
getItemLayout
如果每个列表项高度固定,可以提供这个函数,FlatList 就不需要动态测量高度,滚动更流畅。
我们的文章卡片高度不固定(有的有描述,有的没有),所以没有用这个属性。
小结
FlatList 是 React Native 中最常用的列表组件,通过虚拟化技术实现高性能滚动。配合 RefreshControl 实现下拉刷新,配合 onEndReached 实现上拉加载更多,就能构建一个完整的资讯列表页面。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)