React 导航在异步 useEffect() 更新状态后不会重新渲染嵌套抽屉导航器



我正在实现一个身份验证流,其中我访问导航顶层的AsyncStorage以获取用户。Root是保存DrawerNavigator的组件,出于验证逻辑的目的,我想将我的用户参数传递给它。

// 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, props, navigation}) => {
const user = route.params ? route.params.user : undefined;
console.log('User?', user, props, route)
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 renders, user is undefined (expected)
2. root.js renders, user is undefined (expected)
3. useEffect hook finishes it's async operation (expected)
4. app.js renders, user is defined (expected)
5. *No Rerender On Root.js* (unexpected)

我在StackScreen的initialParams中使用用户对象,所以我预计它会导致root的渲染,但我猜不是。

如何将user对象传递给其余的导航组件?我想让它尽可能简单,我更愿意避免使用减速器(如ReactNavigation示例使用),我希望类似于我的方法将工作。提前感谢!

React导航只在注册和初始化屏幕时分配initialParams值。在这个阶段,它是undefined,当App.js组件获取用户数据并呈现子组件时,包括导航屏幕。initialParams将不会更新,因为这是下一个渲染而不是屏幕初始化阶段。

因为你不想使用任何全局状态管理,如Redux或React上下文API,你应该将user道具直接传递给Root屏幕。

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={(props)= <Root user={user} {...props}/>} options={{ headerShown: false }} />
<Stack.Screen name="Product" component={ ProductScreen } options={ defaultOptions } />
</Stack.Navigator>
</NavigationContainer>
);
};
// root.js
const Root = ({route, navigation,user}) => {
console.log('User?', user, route)
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>
);
}