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

React Navigation异步useEffect更新状态后嵌套抽屉导航不重渲染

问题描述

我在做认证流程,在导航顶层用AsyncStorage获取用户信息。Root组件是DrawerNavigator的容器,想把user参数传进去做认证逻辑。但更新user之后,Root组件没触发重渲染,相关代码和日志如下:

相关代码

app.js

const App: () => Node = () => {
    [user, setUser] = useState();

    useEffect(() => {
        const getUser = async () => {
            let u = await storageHelpers.getUser()
            if(u !== user) setUser(u);
        }
        getUser()
    }, [user])

    console.log('Rendering', user)
    
    return (
        <NavigationContainer>
            <Stack.Navigator>
                <Stack.Screen initialParams={{user: user}} name="Root" component={Root} options={{ headerShown: false }} />
                <Stack.Screen name="Product" component={ ProductScreen } options={ defaultOptions } />
            </Stack.Navigator>
        </NavigationContainer>
    );
};

root.js

const Root = ({route, navigation}) => {
    const user = route.params ? route.params.user : undefined;
    console.log('User?', user)
    return (
        <Drawer.Navigator screenOptions={{drawerPosition:"right"}}>
            <Drawer.Screen name="Home" component={HomeScreen} options={defaultOptions}/>
            <Drawer.Screen name="Scan History" component={ScanHistoryScreen} options={defaultOptions}/>
            <Drawer.Screen name="Languages" component={LanguagesScreen} options={defaultOptions}/>
            <Drawer.Screen name="Report a Problem" component={ReportAProblemScreen} options={defaultOptions}/>
            { user 
                ? <Drawer.Screen name="Logout" component={AuthenticationScreen} />
                : <Drawer.Screen name="Login" component={AuthenticationScreen} />
            }
        </Drawer.Navigator>
    );
}

日志输出

1. app.js渲染,user为undefined(符合预期)
2. root.js渲染,user为undefined(符合预期)
3. useEffect钩子完成异步操作(符合预期)
4. app.js渲染,user已定义(符合预期)
5. *Root.js未触发重渲染*(不符合预期)

问题:用initialParams传递user,更新后Root组件不重渲染,想找简单的传参方式,尽量不用reducer。

解决办法

为什么initialParams不生效?

initialParams只是屏幕首次初始化时传递的参数,父组件state更新后,这个参数不会自动同步,所以Root组件接不到新的user,自然不会触发重渲染。

办法1:直接用render传递props(最简单)

修改app.js里的Root屏幕配置,不用component属性,改用render方法直接渲染Root并传递user:

// app.js中Root屏幕的配置
<Stack.Screen 
    name="Root" 
    options={{ headerShown: false }}
>
    {() => <Root user={user} />}
</Stack.Screen>

然后修改root.js,直接从props中获取user:

// root.js
const Root = ({user, navigation}) => {
    console.log('User?', user)
    return (
        <Drawer.Navigator screenOptions={{drawerPosition:"right"}}>
            {/* 其余代码保持不变 */}
            { user 
                ? <Drawer.Screen name="Logout" component={AuthenticationScreen} />
                : <Drawer.Screen name="Login" component={AuthenticationScreen} />
            }
        </Drawer.Navigator>
    );
}

这样当user更新时,Root组件会跟着重渲染,直接解决问题。

办法2:用React Context(适合多组件需要user的场景)

如果不止Root组件需要用到user,创建全局Context会更方便:

  1. 新建UserContext.js:
import { createContext, useContext } from 'react';

const UserContext = createContext();

export const UserProvider = ({children, user}) => {
    return (
        <UserContext.Provider value={user}>
            {children}
        </UserContext.Provider>
    );
};

export const useUser = () => useContext(UserContext);
  1. 在app.js中用UserProvider包裹导航容器:
const App: () => Node = () => {
    [user, setUser] = useState();

    useEffect(() => {
        const getUser = async () => {
            let u = await storageHelpers.getUser()
            if(u !== user) setUser(u);
        }
        getUser()
    }, [user])

    console.log('Rendering', user)
    
    return (
        <UserProvider user={user}>
            <NavigationContainer>
                <Stack.Navigator>
                    <Stack.Screen name="Root" component={Root} options={{ headerShown: false }} />
                    <Stack.Screen name="Product" component={ ProductScreen } options={ defaultOptions } />
                </Stack.Navigator>
            </NavigationContainer>
        </UserProvider>
    );
};
  1. 在root.js中通过useUser钩子获取user:
// root.js
import { useUser } from './UserContext';

const Root = ({navigation}) => {
    const user = useUser();
    console.log('User?', user)
    return (
        <Drawer.Navigator screenOptions={{drawerPosition:"right"}}>
            {/* 其余代码保持不变 */}
            { user 
                ? <Drawer.Screen name="Logout" component={AuthenticationScreen} />
                : <Drawer.Screen name="Login" component={AuthenticationScreen} />
            }
        </Drawer.Navigator>
    );
}

这样任何组件只要调用useUser就能拿到最新的user,不用挨个传参。

办法3:主动更新导航参数(不想修改组件结构时用)

在app.js中新增一个useEffect,当user变化时手动更新Root屏幕的参数:

// app.js
import { useNavigation } from '@react-navigation/native';

const App: () => Node = () => {
    const navigation = useNavigation();
    [user, setUser] = useState();

    useEffect(() => {
        const getUser = async () => {
            let u = await storageHelpers.getUser()
            if(u !== user) setUser(u);
        }
        getUser()
    }, [user])

    // user更新时同步更新Root屏幕的参数
    useEffect(() => {
        const currentRoute = navigation.getState().routes.find(route => route.name === 'Root');
        if (currentRoute) {
            navigation.setParams({ user: user });
        }
    }, [user, navigation])

    console.log('Rendering', user)
    
    return (
        <NavigationContainer>
            <Stack.Navigator>
                <Stack.Screen initialParams={{user: user}} name="Root" component={Root} options={{ headerShown: false }} />
                <Stack.Screen name="Product" component={ ProductScreen } options={ defaultOptions } />
            </Stack.Navigator>
        </NavigationContainer>
    );
};

这种方式不用修改Root的传参逻辑,但要确保Root屏幕在导航栈中存在,适合临时救急的场景。

内容的提问来源于stack exchange,提问作者Peege151

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 05:10:32