React Native抽屉布局点击同一导航项两次时如何关闭抽屉?
React Native抽屉布局点击同一导航项两次时如何关闭抽屉?
我看了你的代码,发现几个会导致抽屉行为异常的问题(比如点击同一菜单项时抽屉不关闭、控制台报错等),咱们一步步来修复:
先梳理下核心问题
- 你用了
navigation.navigate但没通过useNavigation钩子获取导航实例,这会直接抛出错误 - 代码里有语法错误(
TransactionList的navigate调用多了一个单引号) - 内部维护的
drawerOpen状态和外部传入的props.open没有同步,会导致状态不一致 - 点击同一菜单项的逻辑可以更简洁,确保抽屉能稳定关闭
修复后的完整代码
import {useNavigation} from '@react-navigation/native'; import { ActivityIndicator, FlatList, Image, ImageBackground, SafeAreaView, TouchableOpacity, View, } from 'react-native'; import {Drawer} from 'react-native-drawer-layout'; import {useState, useEffect} from 'react'; import useSelectedDrawerMenu from '../zustand/useselectedmenu'; // 请确保这些Icon组件已正确导入到项目中 import {IconHome, IconTransactionList, IconBoxBlack, IconChartPie, IconGear, IconRefresh, IconEdit} from './components/Icons'; import {AppText, SectionDivider, COLORS} from './components/UI'; // 自定义UI组件请替换为实际路径 import {userLogin, isLoading} from './store/globalState'; // 全局状态请替换为实际定义 type Props = { open: boolean; onOpen: () => void; onClose: () => void; children: React.ReactNode; }; type NavigationItem = { label: string; icon: React.ReactNode; onClick: () => void; }; export default function AppDrawer(props: Props) { const {menuName, setMenuName} = useSelectedDrawerMenu(); const navigation = useNavigation(); // 新增:获取导航实例 const [drawerOpen, setDrawerOpen] = useState<boolean>(props.open); // 同步外部props.open到内部状态,避免状态不一致 useEffect(() => { setDrawerOpen(props.open); }, [props.open]); useEffect(() => { console.log(`当前选中菜单: ${menuName}`); }, [menuName]); useEffect(() => { console.log(`抽屉状态: ${drawerOpen ? '已打开' : '已关闭'}`); }, [drawerOpen]); // 重新加载数据的示例实现,可根据实际需求修改 const handleLoadData = () => { setDrawerOpen(false); console.log('开始重新加载数据...'); // 这里添加你的数据加载逻辑 }; const listNavigation: NavigationItem[] = [ { label: 'Home', icon: <IconHome />, onClick: () => { setDrawerOpen(false); // 先关闭抽屉,逻辑更统一 if (menuName !== 'Home') { setMenuName('Home'); navigation.navigate('Home'); } } }, { label: 'TransactionList', icon: <IconTransactionList />, onClick: () => { setDrawerOpen(false); if (menuName !== 'TransactionList') { setMenuName('TransactionList'); navigation.navigate('TransactionList'); // 修复:移除多余的单引号 } } }, { label: 'ProductList', icon: <IconBoxBlack />, onClick: () => { setDrawerOpen(false); if (menuName !== 'ProductList') { setMenuName('ProductList'); navigation.replace('ProductList'); } } }, { label: 'Statistics', icon: <IconChartPie />, onClick: () => { setDrawerOpen(false); if (menuName !== 'Statistics') { setMenuName('Statistics'); navigation.navigate('Statistics'); } } }, { label: 'Settings', icon: <IconGear />, onClick: () => { setDrawerOpen(false); if (menuName !== 'Settings') { setMenuName('Settings'); navigation.replace('Settings'); } } }, { label: 'Muat Ulang Data', icon: <IconRefresh />, onClick: handleLoadData, }, ]; const _renderNavigation = (item: NavigationItem) => { return ( <TouchableOpacity style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 16, }} onPress={item.onClick} > {item.icon} <AppText size={'large'} style={{ marginLeft: 10, marginBottom: 5, }} > {item.label} </AppText> </TouchableOpacity> ); }; const UnavailableImage = () => { return ( <View style={{ backgroundColor: '#EFFAFF', width: 150, height: 150, alignSelf: 'center', borderColor: '#E0E0E0', borderWidth: 1, }} > <TouchableOpacity style={{ backgroundColor: COLORS.white, borderRadius: 100, width: 30, height: 30, right: -10, top: -10, elevation: 10, position: 'absolute', borderColor: '#E0E0E0', borderWidth: 1, alignItems: 'center', justifyContent: 'center', }} onPress={() => navigation.replace('ConfigAccount')} > <IconEdit /> </TouchableOpacity> <Image style={{ alignSelf: 'center', width: 80, height: 80, marginTop: '25%', }} source={ userLogin?.photo ? {uri: userLogin?.photo} : require('../assets/images/image-default-store.png') } /> </View> ); }; const AvailableImage = () => { return ( <ImageBackground source={{uri: userLogin?.photo}} style={{ backgroundColor: '#EFFAFF', width: 150, height: 150, alignSelf: 'center', borderColor: '#E0E0E0', borderWidth: 1, }} > <TouchableOpacity style={{ backgroundColor: COLORS.white, borderRadius: 100, width: 30, height: 30, right: -10, top: -10, elevation: 10, position: 'absolute', borderColor: '#E0E0E0', borderWidth: 1, alignItems: 'center', justifyContent: 'center', }} onPress={() => navigation.replace('ConfigAccount')} > <IconEdit /> </TouchableOpacity> </ImageBackground> ); }; return ( <Drawer open={drawerOpen} // 使用内部同步后的状态 onOpen={() => { setDrawerOpen(true); props.onOpen(); }} onClose={() => { setDrawerOpen(false); props.onClose(); }} renderDrawerContent={() => { return ( <SafeAreaView> <View style={{ paddingHorizontal: 16, paddingTop: 10, width: '100%', }} > <View> {!userLogin?.photo && <UnavailableImage />} {userLogin?.photo && <AvailableImage />} <AppText align="center" color={COLORS.primary} weight="bold" style={{ fontSize: 20, lineHeight: 40, marginTop: 2, }} > {userLogin?.name || 'Default Merchant'} </AppText> </View> <SectionDivider style={{ borderColor: '#E0E0E0', marginTop: 2, marginBottom: 12, borderWidth: 1, }} /> <FlatList data={listNavigation} renderItem={({item}) => _renderNavigation(item)} showsHorizontalScrollIndicator={false} /> {isLoading && ( <ActivityIndicator size={'large'} color={COLORS.primary} /> )} </View> </SafeAreaView> ); }} > {props.children} </Drawer> ); }
关键修改点说明
补全导航实例:
新增const navigation = useNavigation();,解决navigation未定义的报错,这是实现页面跳转的基础。同步内外状态:
添加监听props.open的副作用,确保外部组件控制抽屉开关时,内部状态能实时同步,避免出现“外部说要关抽屉,内部还显示打开”的矛盾情况。简化点击逻辑:
把关闭抽屉的操作setDrawerOpen(false)放在点击事件的最开头,不管是不是当前页面,点击菜单项先关抽屉,逻辑更直观,也避免了原代码中可能出现的状态判断遗漏。修复语法错误:
移除了TransactionList跳转时多余的单引号,解决语法报错问题。增强组件可控性:
在Drawer组件的onOpen和onClose回调中同步更新内部状态,同时调用外部传入的回调,保持组件的双向可控性。
这样修改后,不管你是点击当前页面的菜单项,还是切换到其他页面,抽屉都能稳定关闭,页面跳转也能正常工作啦~
内容来源于stack exchange
相关产品推荐
相关产品推荐

