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

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' }],
    }
];

额外说明

  1. 修正了原数据源中子项id与父项重复的问题,确保每个id唯一,避免选中状态混乱
  2. 父项的复选框和展开区域分开绑定事件,点击复选框仅修改选中状态,点击标题仅切换展开状态
  3. 使用函数式更新checkedItems,避免依赖旧状态导致的更新异常
  4. getCheckedItemsData方法会遍历所有类别和子项,返回包含类型、名称、父类别等信息的完整选中数据,方便后续业务处理

内容的提问来源于stack exchange,提问作者Ramanjeet

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 09:40:59