React Native可展开列表组件复选框状态切换及选中项获取问题
问题修复方案
你的代码存在状态分散、事件绑定错误、复选框状态逻辑失效等核心问题,以下是完整的修复方案:
关键修改点
- 将选中状态统一放在父组件管理,避免子组件内部状态与父组件状态冲突
- 拆分父项的「展开/收起」和「复选框点击」事件,避免触发展开时误改选中状态
- 修复子项复选框的状态判断逻辑,绑定正确的选中切换方法
- 添加底部按钮用于获取并处理所有选中项的完整数据
完整修复代码
// Import React import React, { useEffect, useState } from 'react'; // Import required components import { SafeAreaView, LayoutAnimation, StyleSheet, View, Image, Text, ScrollView, UIManager, TouchableOpacity, Platform, } from 'react-native'; import ImageName from '../../../assets/imageName/ImageName'; const ExpandableComponent = ({ item, onClickFunction, isChecked, toggleItem }) => { const [layoutHeight, setLayoutHeight] = useState(0); useEffect(() => { setLayoutHeight(item.isExpanded ? null : 0); }, [item.isExpanded]); return ( <View> {/* 父项头部:拆分复选框和展开区域 */} <View style={styles.header}> {/* 复选框点击区域 */} <TouchableOpacity activeOpacity={0.8} onPress={() => toggleItem(item.id)} style={{ padding: 5 }} > <Image style={{ height: 20, width: 20, margin: 10, tintColor: '#FEC432' }} source={isChecked(item.id) ? ImageName.checked : ImageName.unchecked} /> </TouchableOpacity> {/* 展开/收起点击区域 */} <TouchableOpacity activeOpacity={0.8} onPress={onClickFunction} style={{ flex: 1 }} > <Text style={styles.headerText}>{item.category_name}</Text> </TouchableOpacity> </View> {/* 子项列表 */} <View style={{ height: layoutHeight, overflow: 'hidden' }}> {item.subcategory.map((subItem, key) => ( <TouchableOpacity key={key} style={styles.content} onPress={() => toggleItem(subItem.id)} > <View style={{ flexDirection: 'row', alignItems: 'center' }}> <Image style={{ height: 20, width: 20, margin: 10, tintColor: '#FEC432' }} source={isChecked(subItem.id) ? ImageName.checked : ImageName.unchecked} /> <Text style={styles.headerText}> {key + 1}. {subItem.val} </Text> </View> </TouchableOpacity> ))} </View> </View> ); }; const TestExpandible = () => { const [listDataSource, setListDataSource] = useState(CONTENT); const [checkedItems, setCheckedItems] = useState([]); // 判断项是否被选中 const isChecked = (id) => checkedItems.includes(id); // 切换选中状态(函数式更新避免状态依赖问题) const toggleItem = (id) => { setCheckedItems(prev => prev.includes(id) ? prev.filter(item => item !== id) : [...prev, id] ); }; // 获取所有选中项的完整数据 const getCheckedItemsData = () => { const selectedData = []; listDataSource.forEach(category => { // 收集选中的父类别 if (isChecked(category.id)) { selectedData.push({ type: 'category', id: category.id, name: category.category_name }); } // 收集选中的子类别 category.subcategory.forEach(sub => { if (isChecked(sub.id)) { selectedData.push({ type: 'subcategory', id: sub.id, name: sub.val, parentCategory: category.category_name }); } }); }); console.log('所有选中项:', selectedData); // 这里可添加提交、弹窗展示等后续逻辑 }; if (Platform.OS === 'android') { UIManager.setLayoutAnimationEnabledExperimental(true); } // 切换展开/收起状态 const updateLayout = (index) => { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); const array = [...listDataSource]; array.forEach((item, idx) => { item.isExpanded = idx === index ? !item.isExpanded : false; }); setListDataSource(array); }; return ( <SafeAreaView style={{ flex: 1 }}> <View style={styles.container}> <ScrollView> {listDataSource.map((item, key) => ( <ExpandableComponent key={item.id} onClickFunction={() => updateLayout(key)} item={item} isChecked={isChecked} toggleItem={toggleItem} /> ))} </ScrollView> {/* 底部操作按钮 */} <TouchableOpacity style={styles.button} onPress={getCheckedItemsData} > <Text style={styles.buttonText}>获取所有选中项</Text> </TouchableOpacity> </View> </SafeAreaView> ); }; export default TestExpandible; const styles = StyleSheet.create({ container: { flex: 1, paddingBottom: 80 // 给底部按钮预留空间 }, header: { backgroundColor: '#F5FCFF', padding: 20, flexDirection: 'row', alignItems: 'center' }, headerText: { fontSize: 16, fontWeight: '500', }, content: { paddingLeft: 10, paddingRight: 10, backgroundColor: '#fff', flexDirection: 'row', alignItems: 'center', paddingVertical: 15 }, button: { position: 'absolute', bottom: 20, left: 20, right: 20, backgroundColor: '#FEC432', padding: 15, borderRadius: 8, alignItems: 'center' }, buttonText: { fontSize: 16, fontWeight: '500', color: '#fff' } }); // 修正后的数据源:确保所有id唯一,避免选中状态混乱 const CONTENT = [ { isExpanded: false, category_name: 'Item 1', id: 1, subcategory: [ { id: 11, val: 'Sub Cat 1' }, { id: 13, val: 'Sub Cat 3' }, ], }, { isExpanded: false, category_name: 'Item 2', id: 2, subcategory: [ { id: 24, val: 'Sub Cat 4' }, { id: 25, val: 'Sub Cat 5' }, ], }, { isExpanded: false, category_name: 'Item 3', id: 3, subcategory: [ { id: 37, val: 'Sub Cat 7' }, { id: 39, val: 'Sub Cat 9' }, ], }, { isExpanded: false, category_name: 'Item 4', id: 4, subcategory: [ { id: 410, val: 'Sub Cat 10' }, { id: 412, val: 'Sub Cat 2' }, ], }, { isExpanded: false, category_name: 'Item 5', id: 5, subcategory: [ { id: 513, val: 'Sub Cat 13' }, { id: 515, val: 'Sub Cat 5' }, ], }, { isExpanded: false, category_name: 'Item 6', id: 6, subcategory: [ { id: 617, val: 'Sub Cat 17' }, { id: 618, val: 'Sub Cat 8' }, ], }, { isExpanded: false, category_name: 'Item 7', id: 7, subcategory: [{ id: 720, val: 'Sub Cat 20' }], } ];
额外说明
- 修正了原数据源中子项id与父项重复的问题,确保每个id唯一,避免选中状态混乱
- 父项的复选框和展开区域分开绑定事件,点击复选框仅修改选中状态,点击标题仅切换展开状态
- 使用函数式更新
checkedItems,避免依赖旧状态导致的更新异常 getCheckedItemsData方法会遍历所有类别和子项,返回包含类型、名称、父类别等信息的完整选中数据,方便后续业务处理
内容的提问来源于stack exchange,提问作者Ramanjeet
相关产品推荐
相关产品推荐

