
本文详解如何在 react native 中构建一个带层级结构的可展开列表组件,支持父级与子级复选框独立控制、全选逻辑、动态状态更新,并通过底部按钮获取所有已选项目。
本文详解如何在 react native 中构建一个带层级结构的可展开列表组件,支持父级与子级复选框独立控制、全选逻辑、动态状态更新,并通过底部按钮获取所有已选项目。
在 React Native 中实现一个功能完备的可展开列表(Expandable List)并集成复选框交互,关键在于状态提升 + 组件解耦 + 正确的数据驱动更新。原始代码中存在多个核心问题:checkedItems 状态被错误地分散在子组件内部(导致无法跨组件同步)、isChecked 仅作判断未触发更新、子项点击未调用状态变更函数,且父/子复选框缺乏联动逻辑(如“全选子项时父项自动勾选”等高级需求)。以下提供一套生产就绪的解决方案。
✅ 核心设计原则
- 状态集中管理:所有 checkbox 状态统一存储在顶层组件(LoginScreen),避免子组件维护局部 checkedItems。
- 数据结构增强:原始 CONTENT 数据需扩展 isChecked 字段(父项)和每个 subcategory 子项的 isChecked 字段,确保渲染时可直接读取。
- 复选框封装复用:将复选框抽象为独立 Checkbox 组件,接收 checked 和 onChange,职责单一、易于测试与主题定制。
- 状态更新不可变:使用 setListDataSource(prev => ...) 配合 map + 展开运算符,安全更新嵌套对象。
? 示例代码实现
首先创建可复用的 Checkbox 组件:
// components/Checkbox.tsx
import React from 'react';
import { TouchableOpacity, Image, StyleSheet } from 'react-native';
const Checkbox = ({ checked, onChange }: { checked: boolean; onChange: () => void }) => {
return (
<touchableopacity onpress="{onChange}" style="{styles.container}"><image style="{styles.icon}" source="{checked" require :></image></touchableopacity>
);
};
const styles = StyleSheet.create({
container: { marginRight: 8 },
icon: { width: 20, height: 20, tintColor: '#FEC432' },
});
export default Checkbox;
主页面组件(状态管理中枢):
// screens/LoginScreen.tsx
import React, { useState, useEffect } from 'react';
import {
SafeAreaView,
LayoutAnimation,
StyleSheet,
View,
Text,
ScrollView,
UIManager,
TouchableOpacity,
Platform,
} from 'react-native';
import Checkbox from '../components/Checkbox';
import ExpandableComponent from '../components/ExpandableComponent'; // 封装后的子组件
// 初始化数据(显式添加 isChecked 字段)
const CONTENT = [
{
isExpanded: false,
category_name: 'Item 1',
id: 1,
isChecked: false,
subcategory: [
{ id: 1, val: 'Sub Cat 1', isChecked: false },
{ id: 3, val: 'Sub Cat 3', isChecked: false },
],
},
// ... 其他项同理(均补充 isChecked: false)
];
const LoginScreen = () => {
const [listDataSource, setListDataSource] = useState(CONTENT);
// ✅ 统一处理所有 checkbox 点击(父项或子项)
const toggleItem = (id: number) => {
setListDataSource((prev) =>
prev.map((item) => {
// 匹配父项
if (item.id === id) {
return { ...item, isChecked: !item.isChecked };
}
// 匹配子项
if (item.subcategory.some((sub) => sub.id === id)) {
const updatedSubs = item.subcategory.map((sub) =>
sub.id === id ? { ...sub, isChecked: !sub.isChecked } : sub
);
return { ...item, subcategory: updatedSubs };
}
return item;
})
);
};
// ✅ 获取全部已选项目(含父项 & 子项)
const getCheckedItems = () => {
const result: { id: number; name: string }[] = [];
listDataSource.forEach((item) => {
if (item.isChecked) {
result.push({ id: item.id, name: item.category_name });
}
item.subcategory.forEach((sub) => {
if (sub.isChecked) {
result.push({ id: sub.id, name: sub.val });
}
});
});
console.log('✅ Checked Items:', result);
return result;
};
// ✅ 展开/折叠逻辑(Android 兼容)
useEffect(() => {
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
}, []);
const updateLayout = (index: number) => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setListDataSource((prev) =>
prev.map((item, i) => ({
...item,
isExpanded: i === index ? !item.isExpanded : false,
}))
);
};
return (
<safeareaview style="{styles.container}"><scrollview>
{listDataSource.map((item, key) => (
<expandablecomponent key="{item.id}" item="{item}" onclickfunction="{()"> updateLayout(key)}
onCheckboxChange={toggleItem}
/>
))}
{/* ✅ 底部操作按钮 */}
<view style="{styles.footer}"><touchableopacity style="{styles.button}" onpress="{getCheckedItems}"><text style="{styles.buttonText}">获取所有已选项</text></touchableopacity></view></expandablecomponent></scrollview></safeareaview>
);
};
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#fff' },
footer: { padding: 16, alignItems: 'center' },
button: {
backgroundColor: '#007AFF',
paddingHorizontal: 24,
paddingVertical: 12,
borderRadius: 8,
},
buttonText: { color: '#fff', fontSize: 16, fontWeight: '600' },
});
export default LoginScreen;
ExpandableComponent(纯展示组件,无状态):
// components/ExpandableComponent.tsx
import React, { useEffect, useState } from 'react';
import {
View,
TouchableOpacity,
Text,
StyleSheet,
} from 'react-native';
import Checkbox from './Checkbox';
const ExpandableComponent = ({
item,
onClickFunction,
onCheckboxChange,
}: {
item: any; // 实际应定义 TypeScript 接口
onClickFunction: () => void;
onCheckboxChange: (id: number) => void;
}) => {
const [layoutHeight, setLayoutHeight] = useState(0);
useEffect(() => {
setLayoutHeight(item.isExpanded ? null : 0);
}, [item.isExpanded]);
return (
<view>
{/* 父项 Header */}
<touchableopacity activeopacity="{0.8}" onpress="{onClickFunction}" style="{styles.header}"><view style="{styles.row}"><checkbox checked onchange="{()"> onCheckboxChange(item.id)} />
<text style="{styles.headerText}">{item.category_name}</text></checkbox></view></touchableopacity>
{/* 子项内容区(动画高度) */}
<view style="{[styles.contentContainer," height: layoutheight>
{item.subcategory.map((subItem: any, idx: number) => (
<touchableopacity key="{subItem.id}" style="{styles.subItem}" onpress="{()"> onCheckboxChange(subItem.id)}
>
<view style="{styles.row}"><checkbox checked onchange="{()"> onCheckboxChange(subItem.id)} />
<text style="{styles.subText}">{idx + 1}. {subItem.val}</text></checkbox></view></touchableopacity>
))}
</view></view>
);
};
const styles = StyleSheet.create({
header: { backgroundColor: '#F5FCFF', padding: 20 },
row: { flexDirection: 'row', alignItems: 'center' },
headerText: { fontSize: 16, fontWeight: '500', flex: 1 },
contentContainer: { overflow: 'hidden' },
subItem: { paddingLeft: 10, paddingRight: 10, backgroundColor: '#fff', paddingVertical: 12 },
subText: { fontSize: 16, color: '#333' },
});
export default ExpandableComponent;
⚠️ 注意事项与进阶建议
- 性能优化:若列表项极多(>100),建议结合 FlatList + getItemLayout 实现虚拟滚动。
- 全选/反选逻辑:可在父项复选框 onChange 中批量更新其所有子项的 isChecked 状态,再同步更新父项自身状态。
- TypeScript 强类型:强烈建议为 item 和 subcategory 定义接口(如 ExpandableItem, SubItem),避免 any 类型隐患。
- 图标资源路径:确保 require(...) 中的图片路径正确,推荐使用 @react-native-async-storage/async-storage 或 expo-asset 管理资源。
- 无障碍支持:为 Checkbox 添加 accessibilityRole="checkbox" 和 accessibilityState={{ checked }} 提升可访问性。
该方案彻底解决了原始代码中状态不同步、复选框不可交互、数据结构不匹配等问题,具备清晰的职责划分与良好的可维护性,可直接集成至现有项目中。











