You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 02:22:43