Author: Zen and the Art of Computer Programming
1. Background Introduction
In React Native applications, navigation is the main way to switch between different views of the user interface. The core idea is to use the stack data structure to combine different components to achieve smooth transition between pages. In React Native, the Navigator
component based on React components is provided to implement navigation functions. This article will start with the official documentation, component API and sample code, and describe how to use the Navigator
component for programmatic navigation and use the react-navigation
third-party library to implement Navigator
Encapsulation and optimization of components. In addition, the article will also introduce relevant concepts and techniques based on actual project cases to help readers gain a deeper understanding of the navigation mechanism in React Native.
2. Core concepts and connections
2.1 NavigationStack component
In React Native, the Navigator
component is used to manage jumps between different components. It uses a stack data structure to store these components. Each page is called a “route”. Whenever a page needs to be displayed, a new page needs to be pushed to the top of the stack, and then the page on the top of the stack is displayed. At the same time, an interface is provided to return the previous page or the current page to the top page of the stack.
The property list of the Navigator
component is as follows:
initialRoute
: The initial route object is a JavaScript object, including the following two properties:component
: Indicates the component type that renders the page.params
: The parameter object corresponding to this page.
configureScene
: Optional configuration function, used to customize animation effects, receives aroute
object as a parameter and returns an animation description object.onDidFocus
: Callback function triggered when routing changes.onWillBlur
: A callback function triggered when you are about to leave a page.style
: Set the overall style ofNavigator
.
Among them, the main data structure used by the Navigator
component is NavigationStak
, which is a stack in the form of an array, and the elements in the array are routing objects.
2.2 TabNavigator and DrawerNavigator components
In addition to the Navigator
component, there are two commonly used navigator components: TabNavigator
and DrawerNavigator
. Both are further encapsulations of Navigator
, with differences in design, but both follow the same basic logic: multi-level functionality is achieved by nesting different Screen
components. Navigate, and switch levels by rendering different header menu bars to achieve the level switching effect on the screen.
2.2.1 TabNavigator
The function of TabNavigator
is to create a tabbed navigation bar on the page, which is used to quickly switch between multiple pages. It accepts an attribute named tabs
, which specifies a set of routing objects corresponding to the tab names and paths of the page. Each routing object has an attribute named screen
, which indicates the type of component that renders the page, and an attribute named title
, which indicates the name of the label.
If you want to achieve infinite levels of navigation, you can customize the tab component by setting the tabBarComponent
property. For example, you can create an expandable button bar to support dynamic addition of tabs.
2.2.2 DrawerNavigator
The DrawerNavigator
component is a drawer navigator, similar to the sidebar navigation of iOS or Android systems. It can simulate a drawer switching effect at the edge of the screen. It works by first creating a left drawer and rendering pages at each level; while the right side is a fixed-width panel that presents the main content of the application. As long as the panel on the right slides out of the screen, you can swipe left or click the button to switch the status of the drawer to simulate a drawer-style switching effect.
Unlike TabNavigator
, DrawerNavigator
does not have a built-in tab component, but needs to implement painting and responsive tab switching behaviors by itself. However, DrawerNavigator
can still control the width of the drawer by setting the drawerWidth
property.
3. Detailed explanation of core algorithm principles, specific operation steps, and mathematical model formulas
3.1 Use Navigator component to implement programmatic navigation
First, let’s take a look at how to use the Navigator
component to implement programmatic navigation. Here we assume that there is an App
component, which wraps a navigation structure composed of the bottom TabNavigator component and the page StackNavigator component.
class App extends Component { render() { return ( <View style={<!-- -->{flex: 1}}> {/* Bottom tab navigator */} <BottomTabNavigator /> {/* Page stack navigator */} <PageStackNavigator /> </View> ); } }
Next, let’s take a look at the definition of the page stack navigation component. It accepts three properties:
initialRouteName
: Specifies the first displayed page.routes
: It is a collection of pages, including the name and routing information of the page.navigationOptions
: Function that defines navigation options for each page.
const PageStackNavigator = createStackNavigator({ Home: { screen: HomeScreen, navigationOptions: () => ({ title: 'Home', }), }, Profile: { screen: ProfileScreen, navigationOptions: () => ({ title: 'Profile', }), }, }, { initialRouteName: 'Home' });
In the page stack navigation component, we define two pages: HomeScreen
and ProfileScreen
. Each page is a class component and is declared with the createStackNavigator()
method, which receives two parameters:
- The
HomeScreen
component, and anavigationOptions
attribute, are used to define the title of the page. - The
ProfileScreen
component, and anavigationOptions
attribute, are used to define the title of the page.
Then we use the
component to render the entire page stack navigation structure in the render()
method.
When we need to jump to another page, we call a method like this.props.navigation.navigate('Profile')
. The 'Profile'
parameter here represents the name of the target page, which is the route name.
3.2 Parameter passing of Navigator component
When we jump to another page, we usually want to pass some parameters, such as jumping from one page to a certain location on another page, so that some interactive functions between pages can be achieved. When using the Navigator
component, we can pass parameters through navigation properties.
For example, we have a product details page and we need to select a product from the shopping cart and add it to favorites. At this time, we can pass the product ID and quantity to the shopping cart page on the product details page.
First, we create the CartScreen
component in the App
component to display the shopping cart page.
// CartScreen.js import React from'react'; import PropTypes from 'prop-types'; export default class CartScreen extends React.PureComponent { static propTypes = { route: PropTypes.object.isRequired, navigation: PropTypes.shape({ state: PropTypes.object.isRequired, goBack: PropTypes.func.isRequired, }).isRequired, }; handleAddFavorite = () => { const { productId, quantity } = this.props.route.params; // TODO: Add product to favorite list with given ID and quantity } render() { const { name, price, imageUrl, description } = this.props.route.params; return ( <View style={styles.container}> <Text>{name}</Text> <Image source={<!-- -->{ uri: imageUrl }} style={styles.image} resizeMode="contain" /> <Text>{description}</Text> <Text>{price} USD</Text> <Button title="Add to favorites" onPress={this.handleAddFavorite} /> </View> ); } }
In the above code, we get the product ID and quantity from the navigation attributes, and then save them to the local database. For the convenience of demonstration, we will not make actual network requests for the time being.
Then, we return to the product details page and jump to the shopping cart page through the navigation
attribute.
// ProductDetailScreen.js import React from'react'; import { View, Image, Text, Button } from'react-native'; import styles from './ProductDetailStyles'; export default class ProductDetailScreen extends React.PureComponent { static navigationOptions = ({ navigation }) => ({ headerTitle: navigation.state.params.name, headerRight: ( <Button icon={<!-- -->{ type: "font-awesome", name: "cart", size: 20, color: "#fff" }} onPress={() => console.log("TODO: Go to cart")} /> ) }); handleAddToCartPress = () => { const { id, name, price, imageUrl, description } = this.props.product; this.props.navigation.navigate('Cart', { name, price, imageUrl, description, quantity: 1, productId: id, }); } render() { const { name, price, imageUrl, description } = this.props.product; return ( <View style={styles.container}> <Text>{name}</Text> <Image source={<!-- -->{ uri: imageUrl }} style={styles.image} resizeMode="contain" /> <Text>{description}</Text> <Text>{price} USD</Text> <Button title="Add to cart" onPress={this.handleAddToCartPress} /> </View> ); } }
Here, we pass various information and buttons about the product to the shopping cart page. In this way, when the user clicks the button, we can get the ID and quantity of the product, and then add it to the shopping cart based on this information.
3.3 Navigator component routing event monitoring
In some scenarios, we may need to listen to routing events of the Navigator
component, such as listening to the animation end event of page switching. We can listen to routing events through the addlistener()
method.
For example, we want to do some additional operations after the page switching animation ends, such as saving the data of the current page, etc.
const PageStackNavigator = createStackNavigator({ ... }); PageStackNavigator.router.getScreenOptions = (route) => { let options = {}; if (route.params & amp; & amp; route.params.hideTabBar) { options.header = null; options.tabBarVisible = false; } else { options.headerStyle = { backgroundColor: '#f7f7f7' }; options.headerTitleStyle = { fontWeight: 'bold' }; options.headerTintColor = '#333'; } switch(route.routeName) { case 'Home': break; case 'Profile': break; case 'Cart': break; case 'Favorite': break; } return options; }; const AppContainer = createAppContainer(PageStackNavigator); export default class App extends Component { componentDidMount() { this.listener = this.props.navigation.addListener('didFocus', payload => { const currentRoute = payload.state.routes[payload.state.index]; // Do something when a page is focused }); } componentWillUnmount() { this.listener.remove(); } render() { return ( <AppContainer ref={(navigatorRef) => { this.navigatorRef = navigatorRef; }}/> ); } }
Here we call the addListener()
method on the Router
object to listen for routing events and perform certain operations when the didFocus
event is triggered.
On the PageStackNavigator
component we define a static function named getScreenOptions
, which is used to set different options for each route. For example, we can hide or show the navigation bar and tabs, or change the color of the navigation bar, etc.
On the App
component, we get a reference to the Navigator
component by setting the ref
attribute and bind it to a variable.
Finally, we remove the routing event listener in the componentWillUnmount()
life cycle hook.