React Native ScrollView内嵌FlatList卡顿滚动异常问题求解
需求为实现类似DoorDash商家页的垂直堆叠列表效果:用户可动态填充数组作为FlatList的数据源,每新增条目自动扩展容器高度,页面整体支持上下滚动,垂直展示所有内容。
最初采用ScrollView嵌套scrollEnabled={false}的FlatList实现,出现模拟器严重卡顿、滚动到底部时ScrollView上下跳动的异常。
原有实现代码
const [items, setItems] = React.useState([]) const [newItem, setNewItem] = React.useState(newItem) const newItem = [ { name: null, about: null, picture: null, price: null }] <ScrollView> <SafeAreaView> <FlatList data={items} keyExtractor{(item, index) => index.toString()} scrollEnabled={false} renderItem={renderItems()} /> </SafeAreaView> </ScrollView>
基础新增条目功能可正常运行,但ScrollView内容长度增长后会出现明显卡顿。FlatList条目样式参考:
核心诉求:在ScrollView内嵌FlatList的场景下实现动态数组懒加载,无卡顿问题。
同方向滚动的ScrollView嵌套关闭滚动的FlatList是React Native公认的性能反模式:关闭滚动后FlatList会一次性渲染全量数据,完全丢失虚拟列表的懒加载能力,同时两个滚动容器的高度动态计算会持续冲突,必然引发卡顿、滚动跳动问题。针对需求有两种可落地的实现路径:
方案1:单FlatList作为根滚动容器(优先选择,性能最优)
这类全页垂直滚动的列表场景完全不需要嵌套ScrollView,直接将页面中所有非列表内容(商家头部信息、banner、筛选栏等)全部放入FlatList的ListHeaderComponent属性中,从根源上避免滚动嵌套问题,同时完全保留FlatList的虚拟滚动懒加载能力,哪怕数据量上千也不会卡顿。
参考实现:
import { View, Text, FlatList, SafeAreaView, Image } from 'react-native' // 列表上方的所有非菜品内容统一放到头部组件 const PageHeader = () => { return ( <View style={{ padding: 16 }}> {/* 此处放商家头图、名称、评分、配送信息、分类标签等内容 */} <Text style={{ fontSize: 20, fontWeight: 'bold' }}>商家菜单</Text> </View> ) } // 单条菜品渲染逻辑 const renderItem = ({ item }) => { return ( <View style={{ padding: 16, flexDirection: 'row', gap: 12 }}> <Image source={{ uri: item.picture }} style={{ width: 80, height: 80, borderRadius: 8 }} /> <View style={{ flex: 1 }}> <Text style={{ fontSize: 16, fontWeight: '500' }}>{item.name}</Text> <Text style={{ color: '#666', marginTop: 4 }}>{item.about}</Text> <Text style={{ marginTop: 8, color: '#f00' }}>¥{item.price}</Text> </View> </View> ) } export default function RestaurantPage() { const [items, setItems] = React.useState([]) return ( <SafeAreaView style={{ flex: 1 }}> <FlatList data={items} keyExtractor={(item, index) => item?.id ? item.id.toString() : index.toString()} ListHeaderComponent={<PageHeader />} renderItem={renderItem} initialNumToRender={6} windowSize={5} maxToRenderPerBatch={4} /> </SafeAreaView> ) }
新增条目时直接调用setItems更新数组即可,FlatList会自动计算内容高度,按虚拟列表规则懒加载条目,无额外性能消耗。
方案2:必须保留外层ScrollView时的优化方案
如果页面存在多个同方向滚动区块,必须保留ScrollView作为外层容器,做两个调整即可解决卡顿:
- 替换内置FlatList为
@shopify/flash-list,配置准确的estimatedItemSize属性,关闭滚动时也能保留虚拟渲染能力,高度计算性能比内置FlatList高70%以上 - 修复原有代码的两个性能bug:
keyExtractor后缺少等号,正确写法为keyExtractor={(item, index) => ...}renderItem不可直接传入renderItems()的执行结果,需要传入函数引用renderItem={renderItems},否则每次组件重渲染都会重新执行全量条目的渲染逻辑,加剧卡顿
内容的提问来源于stack exchange,提问作者guyman


