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

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>
  );
}

关键修改点说明

  1. 补全导航实例:
    新增const navigation = useNavigation();,解决navigation未定义的报错,这是实现页面跳转的基础。

  2. 同步内外状态:
    添加监听props.open的副作用,确保外部组件控制抽屉开关时,内部状态能实时同步,避免出现“外部说要关抽屉,内部还显示打开”的矛盾情况。

  3. 简化点击逻辑:
    把关闭抽屉的操作setDrawerOpen(false)放在点击事件的最开头,不管是不是当前页面,点击菜单项先关抽屉,逻辑更直观,也避免了原代码中可能出现的状态判断遗漏。

  4. 修复语法错误:
    移除了TransactionList跳转时多余的单引号,解决语法报错问题。

  5. 增强组件可控性:
    在Drawer组件的onOpen和onClose回调中同步更新内部状态,同时调用外部传入的回调,保持组件的双向可控性。

这样修改后,不管你是点击当前页面的菜单项,还是切换到其他页面,抽屉都能稳定关闭,页面跳转也能正常工作啦~

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.09 03:05:31