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会更方便:
- 新建
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);
- 在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> ); };
- 在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
相关产品推荐
相关产品推荐

