React technical principles and code development practice: navigation and routing in React Native

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 NavigatorEncapsulation 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 a route 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 of Navigator.

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

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 a navigationOptions attribute, are used to define the title of the page.
  • The ProfileScreen component, and a navigationOptions 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.

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.