React Native列表项平滑展开/收起卡顿问题优化方案咨询
问题
在ListView的每个列表项中设置了一个隐藏的View,希望通过点击列表项切换该View的显示/隐藏状态。尝试使用State存储已展开项的ID列表实现该功能,列表项少于30个时运行正常,但项数达50或100个以上时,响应缓慢且屏幕更新时会闪烁。求更优的实现方式,实现流畅无闪烁的View切换效果。
现有实现代码
const [openIds, setOpenIds] = useState([]); const toggleOpenToggleView = (id) => { let idx = openIds.indexOf(id) if (idx === -1) { // if id is not in openIds, add it setOpenIds([...openIds, id]) } else { // if id is in openIds, remove it let pre = [...openIds] pre.splice(idx, 1) setOpenIds(pre) } } const ListItemView = ({item}) => ( <View style={[styles.row, styles.item]}> <TouchableOpacity onPress={() => toggleOpenToggleView(item.id)}> <View style={{flexDirection: 'row', justifyContent: 'space-between',}}> <Text style={styles.itemName}>{item.itemName}</Text> </View> </TouchableOpacity> {openIds.indexOf(item.id) > -1 && <View style={[styles.row, styles.toggleContent]}> <Text>{item.toggleContent}</Text> </View> } </View> ) <SwipeListView data={data} renderItem={({item}) => <ListItemView item={item}/>} keyExtractor={item => item.id}
使用版本
"react": "18.2.0", "react-native": "0.71.7",
优化方案
1. 用对象替代数组存储展开状态
数组的indexOf查找和每次更新时的数组拷贝,在数据量大时会带来额外性能开销。改用对象存储展开状态,键为项的ID,值为布尔值(是否展开),查找和更新的时间复杂度都是O(1),能大幅提升效率。
修改后的状态管理代码:
const [openStates, setOpenStates] = useState({}); const toggleOpenToggleView = (id) => { setOpenStates(prev => ({ ...prev, [id]: !prev[id] // 直接切换对应ID的展开状态 })); }
ListItemView中判断展开状态的部分改为:
{openStates[item.id] && <View style={[styles.row, styles.toggleContent]}> <Text>{item.toggleContent}</Text> </View> }
2. 让ListItemView成为纯组件
当前ListItemView会因为父组件状态变化(openIds更新)而全部重新渲染,即使大部分项的展开状态没变化。用memo包裹ListItemView,自定义比较逻辑,只有当item或展开状态变化时才重新渲染。
修改后的ListItemView:
import { memo } from 'react'; const ListItemView = memo(({item, toggleOpenToggleView, isOpen}) => ( <View style={[styles.row, styles.item]}> <TouchableOpacity onPress={() => toggleOpenToggleView(item.id)}> <View style={{flexDirection: 'row', justifyContent: 'space-between',}}> <Text style={styles.itemName}>{item.itemName}</Text> </View> </TouchableOpacity> {isOpen && <View style={[styles.row, styles.toggleContent]}> <Text>{item.toggleContent}</Text> </View> } </View> ), (prevProps, nextProps) => { // 自定义比较逻辑:只有item变化或展开状态变化时才重新渲染 return prevProps.item.id === nextProps.item.id && prevProps.isOpen === nextProps.isOpen; });
渲染列表时传递isOpen属性:
<SwipeListView data={data} renderItem={({item}) => ( <ListItemView item={item} toggleOpenToggleView={toggleOpenToggleView} isOpen={openStates[item.id]} /> )} keyExtractor={item => item.id} />
3. 优化列表组件的渲染性能
SwipeListView基于FlatList,显式开启removeClippedSubviews,让屏幕外的组件被卸载,减少内存占用和渲染压力:
<SwipeListView data={data} renderItem={({item}) => ( <ListItemView item={item} toggleOpenToggleView={toggleOpenToggleView} isOpen={openStates[item.id]} /> )} keyExtractor={item => item.id} removeClippedSubviews={true} />
4. 避免不必要的状态更新
用useCallback包裹toggleOpenToggleView函数,保证其引用稳定,不会触发ListItemView不必要的重新渲染:
import { useCallback } from 'react'; const toggleOpenToggleView = useCallback((id) => { setOpenStates(prev => ({ ...prev, [id]: !prev[id] })); }, []);
内容的提问来源于stack exchange,提问作者Expert wanna be
相关产品推荐
相关产品推荐

