react-native-collapsible动态渲染多层嵌套折叠菜单异常如何解决
我需要基于API请求返回的数据动态填充菜单,目前已定义示例数据const items,部分菜单项包含子项,且子项可继续嵌套下级子项,因此需要实现多层级嵌套的菜单结构。
我使用react-native-collapsible的Collapsible组件封装了Accordion()折叠组件,针对无子项的菜单封装了AccordionItem()组件。
function App()中会两次渲染菜单项:第一次是手动拼接Accordion和AccordionItem组件实现,第二次是遍历items[]数组,调用RootMenuItem()递归渲染每个顶级菜单项及其所有子项。
目前手动拼接的菜单运行效果符合预期,但通过RootMenuItem()递归渲染的嵌套折叠菜单在Android和iOS端运行异常,仅在snack.io的Web环境测试时功能正常。
请问我的实现存在什么问题?有没有更优的实现方案?
备注:snack.io上测试时动态渲染的项宽度显示异常,可忽略该问题。
问题原因
1. 递归组件作用域错误
你将RootMenuItem递归组件定义在了App组件的内部。React Native中,函数组件每次重渲染都会重新生成内部定义的函数组件,导致递归组件的实例被反复销毁重建,Native端对组件实例变更的敏感度远高于Web端,因此会出现状态错乱、折叠功能失效的问题,而Web端的渲染容错性更高所以暂时正常。
2. 渲染key不唯一
递归渲染时如果仅用数组下标index作为组件key,不同嵌套层级的index会出现重复,React的Diff算法会错误复用同key的组件实例,导致折叠状态错乱。
优化实现方案
第一步:调整组件定义位置
将递归菜单组件移到App组件外部全局定义,避免每次重渲染生成新的组件类型。
第二步:使用唯一稳定key
给每个菜单项分配全局唯一的key,建议用菜单项ID+层级+下标的组合形式,不要单独使用index作为key。
优化后代码示例
import { useState } from 'react'; import { View } from 'react-native'; // 全局定义递归组件,不要放在App内部 const NestedMenuItem = ({ item, level = 0 }) => { const [isCollapsed, setIsCollapsed] = useState(true); const hasChildren = Array.isArray(item.children) && item.children.length > 0; const toggleCollapse = () => setIsCollapsed(prev => !prev); return ( <View style={{ paddingLeft: level * 16 }}> {hasChildren ? ( <Accordion title={item.title} collapsed={isCollapsed} onPress={toggleCollapse} > {item.children.map((child, index) => ( <NestedMenuItem // 全局唯一key,避免复用错误 key={`menu-${level}-${child.id ?? index}`} item={child} level={level + 1} /> ))} </Accordion> ) : ( <AccordionItem title={item.title} /> )} </View> ); }; // App组件内直接调用即可 function App() { return ( <View> {/* 动态渲染部分 */} {items.map((topItem, index) => ( <NestedMenuItem key={`top-menu-${topItem.id ?? index}`} item={topItem} level={0} /> ))} </View> ); }
可选优化
如果后续需要实现手风琴模式(打开一个菜单自动关闭其他菜单),可以将所有节点的展开状态提升到上层,用对象存储,key为菜单项唯一ID,value为折叠状态,避免状态分散在各个子组件中难以维护。
如果嵌套层级过深时react-native-collapsible仍然出现兼容性问题,可以改用React Native自带的LayoutAnimation配合高度动画自定义折叠组件,原生端兼容性更好。
内容的提问来源于stack exchange,提问作者dissar

