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

React Native中useEffect触发页面导航两次问题排查

问题详情

我正在开发一款基于Expo的React Native多页面应用,应用带有可切换页面的固定底部Footer,Footer包含对应三个页面的图标。原本功能正常,添加选中图标高亮功能后出现问题:点击Footer图标时,图标能正常高亮,但页面无法切换。在useEffect中打印activeIcon发现,每次点击后useEffect会触发两次,先输出选中的路由名称,随后又回到初始的MessageFeed,导致导航回初始页面。

const Footer = (props: Props) => {
    const navigation = useNavigation();
    const [activeIcon, setActiveIcon] = useState('MessageFeed');

    useEffect(() => {
        // Navigate to the activeIcon route after state update
        navigation.navigate(activeIcon);
    }, [activeIcon, navigation]);

    const handleIconClick = (routeName) => {
        setActiveIcon(routeName); // Update active icon immediately
    };

    return (
        <View style={{flex: 0.1, flexDirection: 'row', justifyContent: 'space-around', marginTop: 10}}>
            <TouchableOpacity style={styles.bottomBtn} onPress={() => handleIconClick('MyProfile')}>
                <FontAwesomeIcon icon={faUser} size={30} color={activeIcon === 'MyProfile' ? '#fb5b5a' : null}/>
            </TouchableOpacity>
            <TouchableOpacity style={styles.bottomBtn} onPress={() => handleIconClick('MessageFeed')}>
                <FontAwesomeIcon icon={faHouse} size={30} color={activeIcon === 'MessageFeed' ? '#fb5b5a' : null}/>
            </TouchableOpacity>
            <TouchableOpacity style={styles.bottomBtn} onPress={() => handleIconClick('CreateTextMessage')}>
                <FontAwesomeIcon icon={faCirclePlus} size={30} color={activeIcon === 'CreateTextMessage' ? '#fb5b5a' : null}/>
            </TouchableOpacity>
        </View>
    );
}

控制台输出(点击第一个图标时)

LOG  MyProfile
LOG  MessageFeed

问题原因分析
  1. useEffect触发两次的直接原因:Expo/React Native在开发模式下默认启用StrictMode,这个模式会刻意重复执行useEffect等生命周期函数,用于检测潜在的代码问题,所以开发环境下触发两次是正常现象,但这不是页面回退的核心原因。

  2. 页面无法切换的核心问题:Footer组件靠内部状态activeIcon控制导航,但没有和导航栈的实际路由状态同步。当调用navigation.navigate切换页面后,组件树可能因父组件重新渲染导致Footer被重新挂载,此时activeIcon会被重置为初始值MessageFeed,进而触发useEffect再次调用navigation.navigate('MessageFeed'),强制切回初始页面。

简单来说就是:组件内部状态和导航系统状态脱节,组件重渲染时内部状态重置,导致导航回退。


解决方案

方案1:同步导航状态到组件内部状态

监听路由变化,将当前路由名称同步到activeIcon,确保内部状态和实际路由一致:

const Footer = (props: Props) => {
    const navigation = useNavigation();
    const [activeIcon, setActiveIcon] = useState('MessageFeed');

    // 监听路由变化,同步activeIcon
    useEffect(() => {
        const unsubscribe = navigation.addListener('state', () => {
            const currentRoute = navigation.getCurrentRoute()?.name;
            if (currentRoute) {
                setActiveIcon(currentRoute);
            }
        });

        return unsubscribe; // 组件卸载时清理监听
    }, [navigation]);

    const handleIconClick = (routeName) => {
        // 避免重复导航到当前页面
        if (activeIcon !== routeName) {
            navigation.navigate(routeName);
        }
    };

    return (
        <View style={{flex: 0.1, flexDirection: 'row', justifyContent: 'space-around', marginTop: 10}}>
            <TouchableOpacity style={styles.bottomBtn} onPress={() => handleIconClick('MyProfile')}>
                <FontAwesomeIcon icon={faUser} size={30} color={activeIcon === 'MyProfile' ? '#fb5b5a' : null}/>
            </TouchableOpacity>
            <TouchableOpacity style={styles.bottomBtn} onPress={() => handleIconClick('MessageFeed')}>
                <FontAwesomeIcon icon={faHouse} size={30} color={activeIcon === 'MessageFeed' ? '#fb5b5a' : null}/>
            </TouchableOpacity>
            <TouchableOpacity style={styles.bottomBtn} onPress={() => handleIconClick('CreateTextMessage')}>
                <FontAwesomeIcon icon={faCirclePlus} size={30} color={activeIcon === 'CreateTextMessage' ? '#fb5b5a' : null}/>
            </TouchableOpacity>
        </View>
    );
}

方案2:直接使用导航栈状态判断高亮

完全去掉组件内部的activeIcon状态,直接从导航系统获取当前路由名称来控制图标颜色,彻底避免状态同步问题:

const Footer = (props: Props) => {
    const navigation = useNavigation();
    // 获取当前路由名称,默认值设为初始页
    const currentRoute = navigation.getCurrentRoute()?.name || 'MessageFeed';

    const handleIconClick = (routeName) => {
        if (currentRoute !== routeName) {
            navigation.navigate(routeName);
        }
    };

    return (
        <View style={{flex: 0.1, flexDirection: 'row', justifyContent: 'space-around', marginTop: 10}}>
            <TouchableOpacity style={styles.bottomBtn} onPress={() => handleIconClick('MyProfile')}>
                <FontAwesomeIcon icon={faUser} size={30} color={currentRoute === 'MyProfile' ? '#fb5b5a' : null}/>
            </TouchableOpacity>
            <TouchableOpacity style={styles.bottomBtn} onPress={() => handleIconClick('MessageFeed')}>
                <FontAwesomeIcon icon={faHouse} size={30} color={currentRoute === 'MessageFeed' ? '#fb5b5a' : null}/>
            </TouchableOpacity>
            <TouchableOpacity style={styles.bottomBtn} onPress={() => handleIconClick('CreateTextMessage')}>
                <FontAwesomeIcon icon={faCirclePlus} size={30} color={currentRoute === 'CreateTextMessage' ? '#fb5b5a' : null}/>
            </TouchableOpacity>
        </View>
    );
}

内容的提问来源于stack exchange,提问作者Mario Mateaș

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 19:23:24