React Native RecyclerListView 解决列表卡顿问题
目录
React Native (RN) 是 Facebook 开源的跨平台前端解决方案,允许开发者用 React 去开发终端 App。
RN 有 ScrollView 组件来渲染列表数据,ScrollView 是所有组件一次渲染的; 所以 RN 推出了 FlatList,优化可视范围外的组件渲染,然而遇到大量数据时,FlatList 同样无能为力,此时滑动可能感觉不出卡顿,但是逻辑计算那边几乎卡死了,会看到列表正常滑动但是按钮点了没反应的现象。
简介
社区有一个列表组件的解决方案 Flipkart/recyclerlistview。它可以通过数据对等比较和子组件(Item)的宽高,决定是否渲染,或更新渲染。
以最简单的例子,渲染 10 个高度 100 的组件,但是 RecyclerListView 容器高度为 300,则可能只有3个组件能被渲染出来。按这个效果,渲染大列表的问题就迎刃而解了。
安装
本文用的 RecyclerListView 版本 = 3.0.5
。
RecyclerListView 是完全 JS 代码,不需要链接原生代码包。
yarn add recyclerlistview
关键 Props
RecyclerListView 必须传入的参数,用来计算是否渲染。
Props | 描述 |
---|---|
layoutProvider |
布局配置,定义每个子组件的宽高 |
dataProvider |
数据配置,定义组件数据 |
rowRenderer |
渲染方法 |
Example
RecyclerListView 默认视所有的 Item 为 行级元素,即优先横排,宽度不够时做换行。
锁定宽高,水平布局(Row)

锁定宽高,组件不会占满屏幕
这个例子渲染一个包含200条数据的 RecyclerListView,但是 LayoutProvider 已经写死了每个 Item 的宽高是 100,在宽度 375 的屏幕上没有占满。
// App.tsx
import React from 'react';
import { View, Text } from 'react-native';
import { RecyclerListView, DataProvider, LayoutProvider } from 'recyclerlistview';
const RecyclerListViewExample = () => {
const data = [...Array(200).keys()].map(id => `Item ${id}`)
const layoutProvider = new LayoutProvider(
(itemIndex) => {
return 'MY_LAYOUT_TYPE'
},
(itemType, dimension) => {
switch (itemType) {
case 'MY_LAYOUT_TYPE':
dimension.height = 100;
dimension.width = 100;
}
}
);
const [dataProvider, setDataProvider] = React.useState(new DataProvider(
(item1, item2) => item1 !== item2,
).cloneWithRows(data))
return <RecyclerListView
layoutProvider={layoutProvider}
dataProvider={dataProvider}
rowRenderer={(itemType, item) => (
<View style={{ flex: 1, backgroundColor: 'cyan' }}>
<Text>{item}</Text>
</View>
)}
/>
}
export default RecyclerListViewExample;
锁定高度,宽度自动(Grid)
RecyclerListView 自带一个栅格布局库 GridLayoutProvider
,可以轻松实现栅格列表。

宽度自动填满,高度仍然固定
栅格布局下不需要确定宽度(width)了,但仍然要确定高度(height)。
// App.tsx
import React from 'react';
import { View, Text } from 'react-native';
import { RecyclerListView, DataProvider, GridLayoutProvider } from 'recyclerlistview';
const RecyclerListViewExample = () => {
const data = [...Array(200).keys()].map(id => `Item ${id}`)
const layoutProvider = new GridLayoutProvider(
2, // 每行 2 格
(itemIndex) => 'MY_LAYOUT_TYPE',
(itemIndex) => 1, // Item 占 1 格
() => 100, // Item 高度 100
);
const [dataProvider, setDataProvider] = React.useState(new DataProvider(
(item1, item2) => item1 !== item2,
).cloneWithRows(data))
return <RecyclerListView
layoutProvider={layoutProvider}
dataProvider={dataProvider}
rowRenderer={(itemType, item) => (
<View style={{ flex: 1, backgroundColor: 'cyan' }}>
<Text>{item}</Text>
</View>
)}
/>
}
export default RecyclerListViewExample;
宽高都不知道
宽高都不知道的情况下,RecyclerListView 无法知道 Item 有没有离开可视范围。
如果不确定组件的实际宽高,但是能确定 Item 每次渲染都是同样的宽高,则可以用 View 的 onLayout
回调,拿到 Item 渲染后的实际宽高,再让 RecyclerListView 重新渲染。
const onLayout = e => {
// 在回调中拿到宽高了
const { width, height } = e.nativeEvent.layout;
}
const itemView = <View onLayout={onLayout} />
附录与引用
更新 DataProvider 的数据
对于 FlatList 的数据更新,React.useState([])
比较常用。
RecyclerListView 要求的数据源是 DataProvider 对象。DataProvider 有 cloneWithRows
用来定义数据:
// 初始化数据源
const [dataProvider, setDataProvider] = React.useState(new DataProvider(
(row1, row2) => row1 !== row2,
).cloneWithRows([1, 2, 3]))
// 更新数据源
const newDataProvider = dataProvider.cloneWithRows([4, 5, 6])
setDataProvider(newDataProvider)
官方推荐直接用现有的 DataProvider clone 一个新的数据源,以避免组件重新计算。
更新 DataProvider 后,列表自己滚动到某个 Item 的位置
Github Issue: Scroll offset not preserve when update re-render
RecyclerListView 默认在数据源更新后,将 scroll 滚动到某个 Item 的位置。
// 新建 LayoutProvider 时,关掉
const layoutProvider = new LayoutProvider(...);
// 关掉刷新后滚动
layoutProvider.shouldRefreshWithAnchoring = false;