react navigation是使用react native开发APP需要用到的路由库,最近出了5.0版本,api的使用上有较大的变化,特别是路由的配置方式由静态路由改为动态路由的方式。本文主要是对官网的文档进行一下知识性总结。
1. 栈路由的使用
import React, { useEffect } from 'react';
import { View, Text } from "react-native";
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
const Test = () => {
return <View><Text>测试</Text></View>
};
const Home = (props) => {
useEffect(() => {
props.navigation.navigate("Others");
});
return <View><Text>Home</Text></View>
}
const Others = ({ extraData }) => {
const { a } = extraData;
return <View><Text>{a}</Text></View>
}
const App = () => {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='Home' screenOptions={{ title: 'Stack title' }}>
<Stack.Screen name='Test' component={Test} />
<Stack.Screen
name='Home'
component={Home}
options={{
title: 'Home title'
}}
/>
<Stack.Screen name='Others' options={{ title: 'Others title' }}>
{props => <Others {...props} extraData={{ a: 1 }} />}
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
);
};
export default App;
各字段含义及解释:
initialRouteName: 指定路由栈的初始路由组件;
name: 指定路由页面名称(在跳转使用navigation.navigate("name")时会用到);
component: 指定路由页面组件;
options: 指定特定子路由页Navigator相关参数,例如title表示头部显示的标题。需要在Stack.Screen上使用;
screenOptions: 指定路由栈中所有路由页Navigator相关参数。需要在Stack.Navigator上使用;
如果在页面栈初始化时,希望给每个路由页面传递指定的默认参数,可以使用如下形式的写法:
<Stack.Screen name='Others' options={{ title: 'Others title' }}>
{props => <Others {...props} extraData={{ a: 1 }} />}
</Stack.Screen>
在对应的页面组件Others中,我们可以通过extraData获取到传递过来的默认值。
但是,这种写法会导致react navigation对路由页的渲染优化失效,所以在自定义页面组件时,需要使用React.memo或React.PureComponent来进行优化。所以在上面的例子中,Others组件最好这样定义:
const Others = React.memo(({ extraData }) => {
const { a } = extraData;
return <View><Text>{a}</Text></View>
}, (prevProps, nextProps) => {
});
第二个参数是自定义比较函数。默认情况下只会对复杂对象进行浅层比较。
2. 子路由页面间的跳转与回退
1、跳转
使用:props.navigation.navigate('routeName'); 进行跳转。
针对某些场景,navigate函数会有以下行为:
(1)指定的routeName并不属于某个路由页面组件时,什么都不会发生。
(2)指定的routeName是当前路由栈中顶部的路由页面组件(即当前所处的页面),什么都不会发生。如果想要不断的把当前页面加入路由栈,可以使用 props.navigation.push('routeName'); 来实现。
2、回退
回退上一页使用:props.navigation.goBack(); 或 props.navigation.pop();
回退路由栈历史中的某个页面:props.navigation.navigate('routeName');
回退路由栈初始页面: props.navigation.popToTop();
3. 路由参数的传递与获取
跳转时指定路由参数: props.navigation.navigate('routeName', { paramsKey: paramsValue });
目标路由页面获取路由参数:props.route.params
如果想在路由页面初始化时,就传递路由初始参数,可以在Stack.Screen的initialParams中指定:
<Stack.Screen
name="Home"
component={Home}
initialParams={{ paramsKey: paramsValue }}
/>
路由初始参数会与在路由时与指定的路由参数进行合并,如果出现同名参数,后者的优先级更高。
4. 自定义头部导航栏
如果想要定义某个页面组件的头部导航栏属性,可以通过options对象传递参数来完成。使用options有以下两种方式:
(1)options={{ title: "xxx" }}
(2)options={({ route }) => ({ title: route.params.title })}
如果想使用路由参数,动态的设置路由页组件头部标题,可以使用第二种方式。
在已挂载页面组件中设置头部导航栏选项可以使用 props.navigation.setOptions({ title: "dynamic title" }); 类似于setState的用法。
options中允许设置的其他导航栏选项:
(1)headerStyle:header容器的样式
(2)headerTintColor:定义header中返回图标以及title的颜色
(3)headerTitleStyle:可以用于额外定义title的字体、大小等属性
(4)headerTitle:自定义title组件,形式如下:headerTitle: props => <LogoTitle {...props} /> /
(5)header:自定义导航栏组件,此时需要在headerStyle中设置自定义header的高度,并将headerMode设置成screen。
如果想在路由页面中完全使用自定义的Header,可以在Stack.Navigator上设置screenOptions={{ header: () => null }},这样路由栈中所有的Header都可以在具体组件中自定义。
详细属性含义可以参考文档:https://reactnavigation.org/docs/stack-navigator/#props
示例代码:
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
title: 'My home',
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}
/>
</Stack.Navigator>
);
}
全局应用Header属性,需要在Stack.Navigator中使用screenOptions,例如:
function StackScreen() {
return (
<Stack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}
>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'My home' }}
/>
</Stack.Navigator>
);
}
5. 嵌套的路由栈
嵌套的路由栈实际上就是,路由栈中某个路由组件实际上指向的是另一个路由栈。例如:
function Home() {
return (
<Tab.Navigator>
<Tab.Screen name="Feed" component={Feed} />
<Tab.Screen name="Messages" component={Messages} />
</Tab.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Profile" component={Profile} />
<Stack.Screen name="Settings" component={Settings} />
</Stack.Navigator>
</NavigationContainer>
);
}
上面例子中,在栈路由中,首页实际上是一个标签路由,标签路由与其所处路由栈中的其他路由页面都是平级关系,具体到跳转表现上就是,从Tab路由页面向其他栈路由页面跳转,底部Tab不会显示。
使用嵌套的路由时候,需要注意如下几点:
(1)每一个路由栈都持有其路由历史,具体点就是,当前处于哪个路由栈里,返回的具体页面由当前所处路由栈的路由历史决定,而无论当前路由栈所处的深度。
(2)跳转动作(Navigator actions)会优先由所处的路由栈进行处理,如果当前路由栈无法处理,将会冒泡至父级路由栈去做处理。
(3)某些路由栈的特定方法仅在其子路由中可见,例如drawer navigator抽屉路由栈中的openDrawer, closeDrawer方法会对其子路由可见。
(4)嵌套的路由栈无法接收父级路由栈的事件,例如某个Tab路由栈中包含一个Stack路由,此时Tab路由的事件例如tabPress对Stack路由并不可见,如果确实想监听父级路由事件,可以使用如下方法监听:
props.navigation.dangerouslyGetParent().addListener();
嵌套子路由的跳转,例如:
function Root() {
return (
<Stack.Navigator>
<Stack.Screen name="Profile" component={Profile} />
<Stack.Screen name="Settings" component={Settings} />
</Stack.Navigator>/
);
}
function App() {
return (
<NavigationContainer>
<Drawer.Navigator>
<Drawer.Screen name="Home" component={Home} />
<Drawer.Screen name="Root" component={Root} />
</Drawer.Navigator>
</NavigationContainer>
);
}
如果使用props.navigation.navigate('Root');会跳转到Root路由栈的初始路由页面,即Profile。
如果想要跳转到子路由栈的其他路由页面,可以这样:
props.navigation.navigate('Root', { screen: 'Settings' });
这样就可以从父路由栈的路由页面中直接跳转到子路由栈中指定的路由页面。
如果想传递参数,可以这样:
props.navigation.navigate('Root', { screen: 'Settings', params: { paramsKey: paramsValue } });
如果想要跳入更深层次的路由页面,可以这样:
props.navigation.navigate('Root', {
screen: 'Settings',
params: {
screen: 'Sound',
params: {
screen: 'Media',
},
},
});
嵌套路由的最佳实践:尽可能的减少嵌套路由的使用以及嵌套深度。过多的嵌套不仅难以理解和追踪页面跳转,而且可能会在低配置的手机上出现性能问题。
6. 路由页面的生命周期
两个事件:focus,blur。
使用:
function Profile({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// Screen was focused
// Do something
});
return unsubscribe;
}, [navigation]);
return <ProfileContent />;
}
或者可以使用react navigation为我们提供的useFocusEffect以及useIsFocused等react hook。