前言

在App中,底部TabBar导航和顶部的导航栏导航是最常见的页面导航方式,而React Native官方推荐的第三方库是@react-navigation,正好今年React Navigation发布了5.0版本,与前面的版本差别还挺大,不过使用上更方便了。本文主要讲解React Navigation5.0及以上版本的使用。

安装

安装核心包

1
2
3
4
# NPM
npm install @react-navigation/native
# Yarn
yarn add @react-navigation/native

安装依赖

1
2
3
4
5
# NPM
npm install react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view
#Yarn
yarn add react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view

React Native 0.60及以上版本是自动链接库的,不需要手动运行react-native link,但如果你用React Native开发的是iOS,还需要手动安装pods来完成库的链接:

1
npx pod-install ios

把下面这行代码放在入口文件的顶部,比如index.js或者App.js

1
import 'react-native-gesture-handler';

最后需要用NavigationContainer将整个App包裹起来,这个代码一般也是放在入口文件,类似下面这样:

1
2
3
4
5
6
7
8
9
import 'react-native-gesture-handler';
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';

export default function App() {
return (
<NavigationContainer>{/* Rest of your app code */}</NavigationContainer>
);
}

注意: 如果你同时也使用了Redux框架,需要把Provider放在最外层,将NavigationContainer包裹在次外层,类似下面这样:

1
2
3
4
5
6
7
8
9
export default class App() {
return (
<Provider store={store}>
<NavigationContainer>
{/* Screen configuration */}
</NavigationContainer>
</Provider>
);
}

使用

React Navigation有多种导航方式:Stack NavigationTab NavigationDrawer Navigation。这里主要讲Stack NavigationTab Navigation

顶部导航栏导航

首先需要安装相应的库:

1
2
3
4
#NPM
npm install @react-navigation/stack
#Yarn
yarn add @react-navigation/stack

Stack Navigation使用上相对来说比较简单,只需要把需要导航的页面组件封装成Stack.Screen,然后包裹在Stack.Navigator就可以了,最上面的Stack.Screen就是默认页面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

const Stack = createStackNavigator();

function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen key="Home" name="Home" component={HomeScreen} />
</Stack.Navigator>
<Stack.Screen key="detail" name="Detail" component={DetailScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}

export default App;

也可以在Stack.Screen中设置导航栏的属性,设置导航栏属性options有两种方式,一种是带route参数的,一种是直接设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/* 带route参数的options */
<Stack.Screen
key={name}
name={name}
component={component}
options={({route}) => ({
headerTitle: route.name,
headerStyle: {
backgroundColor: '#01aaff',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
headerRight: () => (
<Button
onPress={() => alert('This is a button!')}
title="Info"
color="#fff"
/>
),
headerShown: true
})}
/>
/* 直接设置options */
<Stack.Screen
key={name}
name={name}
component={component}
options={{
headerTitle: 'name',
headerStyle: {
backgroundColor: '#01aaff',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
headerRight: () => (
<Button
onPress={() => alert('This is a button!')}
title="Info"
color="#fff"
/>
),
headerShown: true
}}
/>

注意: React框架使用的是虚拟DOM,使用diff算法刷新页面显示,在Stack.Screen中,需要添加key属性,否则会报警告。

在需要跳转到其他页面时只需要以下代码就可以了,后面跟的是参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function HomeScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button
title="Go to Details"
onPress={() => {
/* 1. Navigate to the Details route with params */
navigation.navigate('Details', {
itemId: 86,
otherParam: 'anything you want here',
});
}}
/>
</View>
);
}

如果是用class方式创建的页面,代码会像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export default class HomeScreen extends Component {
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button
title="Go to Details"
onPress={() => {
this.props.navigation.navigate('Details', {
itemId: 86,
otherParam: 'anything you want here',
});
}}
/>
</View>
);
}
}

注意: 导航跳转可以使用navigation.navigate或者navigation.push。如果使用navigate,会查找当前堆栈中是否有名字一样的路由,如果没有才创建新的路由并跳转,而push则会直接创建一个新的路由并跳转,也就是说可以重复多次跳转同一个页面。

堆栈导航的返回方式如下:

1
2
3
4
// 返回到上一页面
navigation.goBack()
// 返回到堆栈里第一个页面
navigation.popToPop()

底部导航栏导航

除了顶部导航栏,最常用的就是底部导航栏了,首先也需要安装相应的库:

1
2
3
4
# NPM
npm install @react-navigation/bottom-tabs
# Yarn
yarn add @react-navigation/bottom-tabs

底部导航栏的使用方法类似,需要将这些页面组件包装成Tab.Screen,导航则由框架内部完成,不需要手动控制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

const Tab = createBottomTabNavigator();

export default function App() {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
</NavigationContainer>
);
}

底部导航栏属性可以按如下方式设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName;

if (route.name === 'Home') {
iconName = focused
? 'ios-information-circle'
: 'ios-information-circle-outline';
} else if (route.name === 'Settings') {
iconName = focused ? 'ios-list-box' : 'ios-list';
}
return <Ionicons name={iconName} size={size} color={color} />;
},
})}
tabBarOptions={{
activeTintColor: 'tomato',
inactiveTintColor: 'gray',
}}
>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>

顶部堆栈导航栏与底部导航栏嵌套

Stack NavigationTab Navigation可以相互多层嵌套,比如登录页跳转到带底部导航栏的主页面,需要把登录相关面和Tab.navigator一同放到同一个堆栈中,但官方不推荐这种方式,嵌套层次太多会导致维护起来特别麻烦。官方推荐使用一个变量控制当前显示的页面,把不相关的模块隔离在不同的堆栈中,类似下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const commonScreens = {
Help: HelpScreen,
};

const authScreens = {
SignIn: SignInScreen,
SignUp: SignUpScreen,
};

const userScreens = {
Home: HomeScreen,
Profile: ProfileScreen,
};

<Stack.Navigator>
{Object.entries({
...commonScreens,
...(isLoggedIn ? userScreens : authScreens),
}).map(([name, component]) => (
<Stack.Screen name={name} component={component} />
))}
</Stack.Navigator>;

isLoggedIn这样的全局变量动态刷新页面使用Redux架构实现起来会简单一些,具体实现可以参考这篇教程