After 2 weeks of basic learning (Flexbox, React.js, JSX, JavaScript), I wanted to improve React Native development through a practical project, so I found the following project:

I. Project introduction

This is a Demo of the course I wrote after learning a popular React Native tutorial by Professor Jia Penghui on MOOCs. This React Native course is very popular on MOOCs website. After reading the course introduction and course arrangement, I thought the explanation was very comprehensive, so I bought it without hesitation.

It took more than two weeks to watch videos, type codes, reconstruct and fix bugs. Except for calling UMeng’s SDK and integrating CodePush, all other parts were basically completed. JavaScript code accounted for 95%, which was basically a pure React Native project. And it runs on both iOS and Android devices:

What’s interesting about this project is that it can switch between multiple themes:

The technical implementation of topic switching is described below.

Use a GIF to walk through the general requirements:

You can run the project as shown in the README file.

Uploading to GitHub has been approved by Teacher Jia

It’s worth noting that this course is a good value for money for developers who want to get started with React Native. Although I upload Demo video is available in most of the functionality, but after debugging, the modified code are limited amount of information, and the teacher is in a lot of knowledge about the actual development explained in this video is not reflected in the code, so suggest you sign up for classes to improve their level of development.

Key technical points of React Native development

Let’s start with a mind map for section 2:

2.1 The idea of componentization

React Native is React’s cross-platform solution on mobile devices. If you want to understand React Native development quickly, you must first understand React.

React is an open source front end framework for FaceBook. It originated as an internal project at FaceBook and was opened in May 2013. React has attracted more and more people because of its high performance and very simple code logic. Currently, the framework has 70,000 + stars on Github.

React adopts a component-based development approach. By building views into components, codes are easier to reuse and can be well used in large project development. In React, building apps is like building blocks.

Therefore, if you want to learn React Native, you must first understand the components in React.

So what is a component?

In React, each module is defined as a component on the UI that has relatively independent functions. Relatively small components can be combined or nested to form larger components to build the overall UI.

Thus, the entire UI is a large component made up of widgets, each of which cares only about its own part of the logic, independent of each other.

React believes that a component should have the following characteristics:

  • Composeable: A component is easy to use with other components, or nested within another component. If a component creates another component internally, then the parent component owns the child component it creates. By this feature, a complex UI can be split into multiple simple UI components.
  • Reusable: Each component is functional and can be used in multiple UI scenarios.
  • Maintainable: Each small component contains only its own logic and is easier to understand and maintain;

For example, 🌰, let’s take a look at the navigation bar used by the Demo:

A wrapped navigation bar can be called a component, and it meets the above three characteristics:

  1. Composable: Navigation bar components can be placed within a page component as child components of the page component. And inside the navigation bar component, there are button components and other sub-components.
  2. Reusable: If encapsulated, this component can be used on any page (component) that requires a navigation bar, or in other projects.
  3. Maintainable: Easy to locate and modify because of its independent functionality and presentation logic.

Now that you understand the basic concepts of components, let’s take a look at some other components.

2.2 Component properties and states

In React Native (React.js), components hold two types of data:

  1. Properties (props) : The props of a component are immutable and can only be passed from other components, such as the parent component.
  2. State: The component’s state is mutable, and it handles the interaction with the user. The current component fires if it changes a state of the current component after, for example, a user clicks on an eventrender()Method to refresh itself.

Here’s an example of the project’s favorites page:

We can see that this page has two sub-pages, one is the ‘hottest’ page (component) and the other is the ‘Trending’ page (component). What props and state do these two components have?

props

Because props is passed from its parent, it is conceivable that the props declaration should be made in the parent of the current component. In React Native, usually the props declaration is placed with the current component declaration:

// The hottest sub-page<FavoriteTabPage {... this.props} tabLabel='hot' flag={FlAG_STORAGE.flag_popular}/>

// Trend subpage<FavoriteTabPage {... this.props} tabLabel='trend' flag={FlAG_STORAGE.flag_trending}/>
Copy the code

Here, the favorite page is the parent component, and the hottest and Trending pages are its children. A component that declares the hottest and trending pages in the favorite page component.

Also, we can see that the FavoriteTabPage and the trending page both use the same component: FavoriteTabPage. The only difference between the two pages is the difference between the two props passed in: tabLabel and Flag.

In the FavoriteTabPage component, if you want to call flag, you can use this.props. Flag.

Let’s look at state:

state

Here are the components of the hottest and trending pages:

class FavoriteTabPage extends Component{

    // The component's constructor
    constructor(props){

        super(props);
        this.state={
            dataSource:new ListView.DataSource({rowHasChanged:(r1,r2) = >r1! ==r2}),isLoading:false,}}... }Copy the code

There are two states defined:

  1. DataSource: dataSource of the list
  2. IsLoading: Whether the device is being refreshed

Both of these states are likely to change frequently in the future. For example, after a network request, the data source of the list will be replaced

 this.setState({
      // Pass the new value newDataArr object to the dataSource
      dataSource:newDataArr
 })
Copy the code

To trigger the Render () method to refresh the list component.

2.3 Component life cycle

Similar to the ViewController life cycle in iOS development, components have a life cycle, which is roughly divided into three phases:

  • Mounting: The real DOM is inserted
  • Updating: Being rerendered
  • Unmounting: The real DOM has been removed

DOM is a front-end concept that can be roughly understood as a tree structure for a page.

Each phase has its corresponding state and corresponding callback function, as shown in the following figure:

React Native: React Native

React provides two callbacks for each state. The will function is called before entering the state and the DID function is called after entering the state.

Here are some of the most important callbacks:

render()

This function is the render callback of the component that must be implemented and must return a component or a component with multiple child components.

Note: This function can be called multiple times: it is called both for initialization and after state changes.

componentDidMount()

It is called immediately after the initial render execution, that is, when this function is called, the current component has already been rendered, which is equivalent to the viewDidLoad method in ViewController in iOS development.

We usually perform network request operations in this method.

componentWillReceiveProps(object nextProps)

Called when the current component receives new props. This function can be used as an opportunity for React to update state after prop is passed in and before render() is rendered. The new props can be obtained from the parameter, and the old props can be obtained from this. Props.

Note: This method is not called when the render is initialized.

shouldComponentUpdate(object nextProps, object nextState):

Called before the new props or state is received and is about to render. If it is determined that the new props and state will not cause the component to update, return false so that the component will not update, reducing unnecessary performance losses.

Note: This method is not called when initializing the render.

componentWillUnmount()

Called immediately when the component is removed from the DOM. For example, when the current page clicks the back button to jump to the previous page.

We usually remove notifications in this method. More on this later.

Now that we’ve covered some components, let’s look at how we can use components to build interfaces.

2.4 Use components to build interfaces

Here are a few examples of how to set up a View in React Native.

First let’s take a look at the layout of the cell on the most popular page:

2.41 Setting up cell Components

First, take a cell in the list of the most popular TAB pages as an example to explain how a simple UI component is implemented:

Let’s call this component RespositoryCell and see how it works with the code:


export default class RespositoryCell extends Component{... render(){// Get the current cell's data and assign it to the item
        let item = this.props.projectModel.item?this.props.projectModel.item:this.props.projectModel;

        // Bookmark button
        letfavoriteButton = <TouchableOpacity onPress={()=>this.onPressFavorite()} > <Image style={[styles.favoriteImageStyle,this.props.theme.styles.tabBarSelectedIcon]} source={this.state.favoriteIcon} /> </TouchableOpacity> return(<TouchableOpacity onPress={this.props. OnSelect} style={styles.container} > // View the entire cell <View style={styles.cellContainerViewStyle}> //1. The project name < Text style = {styles. RepositoryTitleStyle} > {item. Full_name} < / Text > / / 2. Project introduction < Text style = {styles. RepositoryDescriptionStyle} > {item. The description} < / Text > / / 3. At the bottom of the container < View style = {styles. BottomContainerViewStyle} > / author / 3.1 container < View Style = {styles. AuthorContainerViewStyle} > / / 3.11 the Author name < Text style = {styles. BottomTextStyle} > Author: < / Text > / / 3.12 the Author portraits < Image style = {styles. AuthorAvatarImageStyle} source = {{uri: item. Owner. Avatar_url}} / > < / View > / / 3.2 star container < View Style = {styles. StarContainerViewStyle} > / / 3.21 star title < Text style = {styles. BottomTextStyle} > Starts: < / Text > / / 3.21 star number <Text style={styles.bottomTextStyle}>{item.stargazers_count}</Text> </View> //3.3 {favoriteButton} </View> </View> </TouchableOpacity>) } }Copy the code

Functions that handle interaction events are omitted to focus on the layout and style of the cell.

  • It’s declared hereRespositoryCellComponent that inherits fromComponentThat is, components must always inherit from this class when they are declared.
  • Focusing on the render method of this component, it returns the actual layout of the component: JSX is synthetically used, htMl-like tagging syntax that clearly shows the hierarchy of the cell: – The outermost layer is covered by aViewThe component is wrapped with three child components in the first layer: twoTextComponent and one for the bottom backgroundViewComponents. – Bottom backgroundViewA component has three child components:ViewComponent (displays author information),ViewComponent (display star information), favorites button.

Try looking at the following image in combination with the code, and you can see that the actual layout of the components is highly consistent with the layout of the code:

However, it is not enough to define the hierarchy of the component. We also need to define the style of the component (such as the size style of the image component, etc.). In this case, we define some styles to be used by defining a style object (usually using a constant object) :

// Style constants
const styles =StyleSheet.create({

    // Project cell background view style
    cellContainerViewStyle:{
          
        / / the background color
        backgroundColor:'white'./ / padding
        padding:10./ / from the outside
        marginTop:4.marginLeft:6.marginRight:6.marginVertical:2./ / frame
        borderWidth:0.3.borderColor:'#dddddd'.borderRadius:1./ / iOS shadows
        shadowColor:'#b5b5b5'.shadowOffset: {width:3.height:2},
        shadowOpacity:0.4.shadowRadius:1./ / Android's shadow
        elevation:2
    },
  
    // The style of the project title
    repositoryTitleStyle:{
        fontSize:15.marginBottom:2.color:'# 212121',},// Project introduction style
    repositoryDescriptionStyle:{
        fontSize:12.marginBottom:2.color:'# 757575'
    },

    // The style of the bottom container
    bottomContainerViewStyle:{
        flexDirection:'row'.justifyContent:'space-between'
    },

    // The style of the author container
    authorContainerViewStyle:{
        flexDirection:'row'.alignItems:'center'
    },

    // The style of the author's profile picture
    authorAvatarImageStyle:{
        width:16.height:16
    },

    // Star container style
    starContainerViewStyle: {
        flexDirection:'row'.alignItems:'center'
    },

    // The bottom text style
    bottomTextStyle:{
       fontSize:11,},// Bookmark the button's image style
    favoriteImageStyle:{
        width:18.height:18}})Copy the code

In this code, we define all the styles used by the RespositoryCell component. We modify the component styles by assigning them to the style property of the corresponding child component. For example, let’s look at the project title component and its style definition:

<Text style={styles.repositoryTitleStyle}>{item.full_name}</Text>
Copy the code

Here, we first define a Text component to display the title of the project. Then styles. RepositoryTitleStyle is assigned to the current Text component style, and the specific content of the title, by item. Full_name to obtain.

It is important to note that in JSX syntax, objects need to be wrapped in {}, otherwise they are considered constants. For example, if this were written as:

<Text style={styles.repositoryTitleStyle}>item.full_name</Text>
Copy the code

The title of all project cells will be displayed as ‘item.full_name’.

This is a common mistake for beginners, so be careful to distinguish between objects and constants when building a page. If it is an object, it must be enclosed in braces! If it is an object, it must be enclosed in braces! If it is an object, it must be enclosed in braces!

Flexbox-specific layout attributes such as length, width, inside and outside margins, and flexDirection are not covered here. You can learn by looking for related links at the end of this article.

2.42 Setting up a static table

When setting up personal pages and static table pages in React Native, you can use ScrollView components to wrap encapsulated cell components. Take a look at the Demo’s personal page renderings and code implementation:

Let’s create a new JavaScript file in the project and call it minepage.js. This file is the implementation of the personal page. Let’s look at the implementation in combination with the code (removing the logical processing code that handles clicking cells) :

// Reference area:
// References React, Component(Component class), and components that come with React Native
import React, { Component } from 'react';
import {
    StyleSheet,
    Text,
    View,
    Image,
    ScrollView,
    TouchableHighlight,
} from 'react-native';

// Import other components (page components) and constants defined in the project, with relative paths
import NavigationBar from '.. /.. /common/NavigationBar'
import {MORE_MENU} from '.. /.. /common/MoreMenu'
import GlobalStyles from '.. /.. /.. /res/styles/GlobalStyles'
import ViewUtil from '.. /.. /util/ViewUtils'
import {FLAG_LANGUAGE}from '.. /.. /dao/LanguageDao'
import AboutPage from './AboutPage'

import CustomKeyPage from './CustomKeyPage'
import SortPage from './SortKeyPage'
import AboutMePage from './AboutMePage'
import CustomThemePage from './CustomThemePage'
import BaseComponent from '.. /.. /base/BaseCommon'

// Area 2: page component definition area:
export default class MinePage extends BaseComponent {...// Render a unified function for each cell in the List on the page
    createSettingItem(tag,icon,text){
        return ViewUtil.createSettingItem((a)= >this.onClick(tag),icon,text,this.state.theme.styles.tabBarSelectedIcon,null);
    }

    render(){
        return< the View style = {GlobalStyles. ListViewContainerStyle} > < NavigationBar title = {} 'my' style = {this. State. Theme. Styles. NavBar} / > < ScrollView > {/ * = = = = = = = = = = = = = project information Section = = = = = = = = = = = = = * /} < TouchableHighlight underlayColor = 'transparent' onPress={()=>this.onClick(MORE_MENU.About)} > <View style={styles.itemInfoItemStyle}> <View style={{flexDirection:'row',alignItems:'center'}}> <Image source={require('.. /.. /.. /res/images/ic_trending.png')} style={[{width:40,height:40,marginRight:10},this.state.theme.styles.tabBarSelectedIcon]} /> <Image source={require('.. /.. /.. /res/images/ic_tiaozhuan.png')} style={[{height:22,width:22},this.state.theme.styles.tabBarSelectedIcon]} /> </View> < / TouchableHighlight > {divider / * * /} < View style = {GlobalStyles. CellBottomLineStyle} > < / View > {/ * = = = = = = = = = = = = = trend management Section = = = = = = = = = = = = = * /} < Text style = {styles. GroupTitleStyle} > trend management < / Text > < the View Style = {GlobalStyles. CellBottomLineStyle} > < / View > {/ * * / custom language} {this.createSettingItem(MORE_MENU.Custom_Language,require('.. /.. /.. / res/images/ic_custom_language. PNG '), 'custom language')} < View style = {GlobalStyles. CellBottomLineStyle} > < / View > < the View Style = {GlobalStyles. CellBottomLineStyle} > < / View > sort {/ * * /} {enclosing createSettingItem (MORE_MENU Sort_Language, require (".. /.. /.. / res/images/ic_swap_vert. PNG '), 'language sort')} < View style = {GlobalStyles. CellBottomLineStyle} > < / View > {/ * = = = = = = = = = = = = = label management Section = = = = = = = = = = = = = * /} < Text style = {styles. GroupTitleStyle} > tag management < / Text > < the View Style = {GlobalStyles. CellBottomLineStyle} > < / View > {/ * custom tag * /} {enclosing createSettingItem (MORE_MENU Custom_Key, require (".. /.. /.. / res/images/ic_custom_language. PNG '), 'custom tags')} < View style = {GlobalStyles. CellBottomLineStyle} > < / View > tag sort * /} {/ * {this.createSettingItem(MORE_MENU.Sort_Key,require('.. /.. /.. / res/images/ic_swap_vert. PNG '), 'label sorting')} < View style = {GlobalStyles. CellBottomLineStyle} > < / View > < the View Style = {GlobalStyles. CellBottomLineStyle} > < / View > {/ * tags removed * /} {enclosing createSettingItem (MORE_MENU Remove_Key, require (".. /.. /.. / res/images/ic_remove. PNG '), 'tags removed')} < View style = {GlobalStyles. CellBottomLineStyle} > < / View > {/ * = = = = = = = = = = = = = Settings Section = = = = = = = = = = = = = * /} < Text style = {styles. GroupTitleStyle} > Settings < / Text > {/ * custom theme * /} < View style={GlobalStyles.cellBottomLineStyle}></View> {this.createSettingItem(MORE_MENU.Custom_Theme,require('.. /.. /.. / res/images/ic_view_quilt. PNG '), 'a custom theme')} < View style = {GlobalStyles. CellBottomLineStyle} > < / View > {/ * shows a custom theme page * /} {this.renderCustomTheme()} </ScrollView> </View>}}  const styles = StyleSheet.create({ itemInfoItemStyle:{ flexDirection:'row', justifyContent:'space-between', alignItems:'center', padding:10, height:76, backgroundColor:'white' }, groupTitleStyle:{ marginLeft:10, marginTop:15, marginBottom:6, color:'gray' } });Copy the code

In the code above, we can see the full view of a page component, which is roughly divided into three areas:

  1. Reference area
  2. Defining component areas
  3. Defining the style area

The following two areas were introduced in the previous section. The first section, the reference section, is written at the beginning of a component file, where other components or constants need to be introduced.

Now look at the render() function of this component, which returns the View component that wraps the entire page and has two child components

  • The NavigationBar component, which passes in two props: title and style.
  • ScrollView component, the View component that wraps the project information Cell, splitter line, the View component of the project Cell. Note that the components of each cell are similar, so here we encapsulate the code that generated it to make a function call:
createSettingItem(tag,icon,text){
        return  ViewUtil.createSettingItem((a)= >this.onClick(tag),icon,text,this.state.theme.styles.tabBarSelectedIcon,null);
    }
Copy the code

You can see that this function takes three arguments: a tag for the tag, an image, and a title text. Its return value is achieved by calling the createSettingItem method of the ViewUtil component. This method is used to uniformly generate cells with a similar layout.

Take a look at the implementation of this function:

 
//ViewUtils.js
static createSettingItem(callBack,icon,text,tintColor,expandableIcon){

        // If icon is not passed, it is not displayed
        let image = null;
        if(icon){ image = <Image source={icon} resizeMode='stretch' style={[{width:18,height:18,marginRight:10},tintColor]} /> } return ( <View style={{backgroundColor:'white'}}> <TouchableHighlight onPress={callBack} underlayColor= 'transparent' > <View style={styles.settingItemContainerStyle}> <View style={{flexDirection:'row',alignItems:'center'}}> {image} <Text>{text}</Text> </View> <Image source={expandableIcon? expandableIcon:require('.. /.. / res/images/ic_tiaozhuan. PNG ')} style = {[{marginRight: 0, height: 22, width: 22}, tintColor]} / / brackets / > < / View > </TouchableHighlight> </View> ) }Copy the code

This function takes five arguments:

  • Callback: method called when a cell is clicked, requiring the parent component to pass in
  • Icon: The image on the left of the cell
  • Text: Indicates the cell title
  • TintColor: Theme color of the cell
  • ExpandableIcon: Image to the right of cell (triangle arrow)

Because there is no specific Button component in React Native, implementing component clicks are done by wrapping them around a clickable component like TouchableHighlight.

The View component and the Text component are commonly used to implement the click effect.

Notice the two props passed in TouchableHighlight:

  1. If you want the color to stay the same when you click, you can change itunderlayColorSet totransparent.
  2. You can pass in the function that triggers when you clickonPressProperties. So, if the cell is clicked, the incoming callback is triggered. This callback is the same as the arrow function that was passed:
ViewUtil.createSettingItem((a)= >this.onClick(tag),icon,text,this.state.theme.styles.tabBarSelectedIcon,null);
Copy the code

This function is called on the individual page and is used to jump when the cell is clicked, etc.

Note that in this ViewUtils class, we can define many common View components, such as the cell for setting up the page, the back button on the navigation bar, and so on.

Now that the cell implementation is complete, let’s talk about the splitter line and session title.

Let’s take a look at the dividing line:

<View style={GlobalStyles.cellBottomLineStyle}></View>
Copy the code

Its style calls GlobalStyles’ cellBottomLineStyle. Because GlobalStyles is a global style file (written in a separate JS file), you can use it to manage common styles. In this way, we do not need to declare style constants repeatedly in component pages of different pages.

Let’s look at how to define a global style file:

//GlobalStyles.js
module.exports ={

    // Cell splitter style
    cellBottomLineStyle: {
        height: 0.4.opacity:0.5.backgroundColor: 'darkgray',},// Cell background color style
    cell_container: {
        flex: 1.backgroundColor: 'white'.padding: 10.marginLeft: 5.marginRight: 5.marginVertical: 3.borderColor: '#dddddd'.borderStyle: null.borderWidth: 0.5.borderRadius: 2.shadowColor: 'gray'.shadowOffset: {width:0.5.height: 0.5},
        shadowOpacity: 0.4.shadowRadius: 1.elevation:2
    },

    // Current screen height
    window_height:height,
    // Current screen width
    window_width:width,

};
Copy the code

Thanks to the module.exports method, global styles defined here can be used externally at will.

Finally, the View for Section Title is simpler, just a View component with gray text.

<Text style={styles.groupTitleStyle}> Trend Management </Text>Copy the code

2.43 Build the basic framework of app: TabBar + NavigationBar

NavigationBar is a mainstream global interface solution for mobile apps. However, in the React Native component, there is no component that integrates the two components. Fortunately, there’s a third-party component that does a better job of integrating the two: React-native Tab-Navigator.

NPM install react-native-tab-navigator – save: NPM install react-native-tab-navigator – save However, I recommend using YARN to import all third-party components: YARN Add React-native tab-navigator. The NPM command sometimes causes problems when installing third-party components. In addition, you are advised to use YARN to import third-party components.

After verifying that the React-native Tab-Navigator component has been downloaded to the NPM folder, it is ready to be imported and used in the project. Here’s how to use it:

// Import the react-native tab-navigator component and call it TabNavigator.
import TabNavigator from 'react-native-tab-navigator';

// The unique identifier for each TAB can be obtained externally
export const FLAG_TAB = {
    flag_popularTab: 'flag_popularTab'.flag_trendingTab: 'flag_trendingTab'.flag_favoriteTab: 'flag_favoriteTab'.flag_myTab: 'flag_myTab'
}

export default class HomePage extends BaseComponent {

    constructor(props){
      
        super(props);
        
        let selectedTab = this.props.selectedTab?this.props.selectedTab:FLAG_TAB.flag_popularTab

        this.state = {
            selectedTab:selectedTab,
            theme:this.props.theme
        }
    }

    _renderTab(Component, selectedTab, title, renderIcon) {
        return( <TabNavigator.Item selected={this.state.selectedTab === selectedTab} title={title} selectedTitleStyle={this.state.theme.styles.selectedTitleStyle} renderIcon={() => <Image style={styles.tabItemImageStyle} source={renderIcon}/>} renderSelectedIcon={() => <Image style={[styles.tabItemImageStyle,this.state.theme.styles.tabBarSelectedIcon]} source={renderIcon}/>} onPress={() => this.onSelected(selectedTab)}> <Component {... this.props} theme={this.state.theme} homeComponent={this}/> </TabNavigator.Item> ) } render() { return ( <View Style={styles.container}> <TabNavigator tabBarStyle={{opacity: 0.9,}} sceneStyle={{paddingBottom: 0}} > {this._renderTab(PopularPage, FLAG_TAB. FLAG_TAB, 'hottest ', require('.. /.. /.. /res/images/ic_polular. PNG '))} {this._renderTab(TrendingPage, FLAG_TAB. /.. /.. /res/images/ic_trending. PNG '))} {this._rendertab (FavoritePage, FLAG_TAB. Flag_favoriteTab, 'favorites, require('.. /.. /.. /res/images/ic_favorite. PNG '))} {this._rendertab (MinePage, FLAG_TAB. Flag_myTab, 'my ', require('.. /.. /.. /res/images/ic_my.png'))} </TabNavigator> </View> ) } }Copy the code

I’ve omitted the rest of the code here, except for the TabBar && NavigationBar setup.

The HomePage component defined here is the one that the Demo uses to manage these tabs.

Because the Demo has four tabs in total, we extract the code for the rendered TAB as a single function: _renderTab. This function takes four arguments:

  • Component: Component displayed when the current TAB is clicked.
  • SelectedTab: unique identifier of the current TAB.
  • Title: title of the current TAB.
  • RenderIcon: The icon of the current TAB.

In the _renderTab method, we return a tabnavigator. Item component, and we fill it with the components that belong to the TAB, in addition to some definition of the TAB props:

<Component {... this.props} theme={this.state.theme} homeComponent={this}/>Copy the code

Here, {… This. Props} is the way to assign all props of the current HomePage to this Component. Two other props were also defined: Theme and homeComponent.

A constant defines the unique identity of the four tabs. Note that this constant can be obtained by other components as it is modified by the export field.

One other thing to note about HomePage is that it has a property called selectedTab, which marks which TAB is currently selected. FLAG_TAB. Flag_popularTab = selectedTab = selectedTab; flag_popularTab = selectedTab;

2.5 Communication between Components

Since the React project is built as a unit of components, there must be data and event transfer between components, that is, communication between components.

Component communication falls into two categories:

  1. Communication between components that are directly or indirectly related

  2. Communication between components that are not directly or indirectly related

​

2.51 Communication between components that are directly or indirectly related

My personal understanding of the relationship between parent and child components is this:

If component A contains component B, or if component B is created within component A, then component A is the parent of component B. Conversely, component B is A child of component A and A directly related component.

Such as:

  • An interface navigation component is a child of the entire page component because it is contained within the current page component.

  • The next page to jump to from this page is a child of the current page: it is contained in the Navigator of the current page component.

​

Together with the communication between subcomponents and subcomponents, direct or indirect communication between relational components falls into the following three categories:

  1. The parent component passes data and events to the child.

  2. Child components pass messages and events to their parents.

  3. Child components pass messages and events to child components.

​

The parent passes data and events to the child: by assigning values to the child’s properties.

As we saw above, we used the navigation component for the page layout:

<NavigationBar
      title={'I'}
      style={this.state.theme.styles.navBar}
 />
Copy the code

Here, the current page components to ‘my’ object, as well as this. The state. The theme. Styles. The navBar objects respectively assigned to the navigation bar component. After the navigation bar receives these two values, it can get them internally via this.props. Title and this.props. Style. In this way, the parent component passes data to the child component.

Child components pass messages, data to parent components: this is done by the parent giving the child a closure (callback)

Take an example of clicking a cell on the hottest TAB page for callback to realize interface jump:

Since the cell component is generated in the hottest TAB page component, the cell component is its child:

// The ListView component generates functions for each cell
renderRow(projectModel){
  return <RespositoryCell
            key = {projectModel.item.id}
            theme={this.state.theme}
            projectModel={projectModel}
            onSelect = {()= >this.onSelectRepository(projectModel)}
            onFavorite={(item,isFavorite)=>this.onFavorite(item,isFavorite)}/>
}
Copy the code

RenderRow () is the function used by the ListView component to render each Cell row, and must return a Cell component. Here we define a custom RespositoryCell component as its Cell component.

As we can see, there are 5 props assigned, of which onSelect and onFavorite are given functions:

  • onSelectThe callback is the function that jumps to the hottest TAB page after clicking on the cellonSelectRepository().
  • onFavoriteThe callback is a function that changes the status of the favorite button corresponding to the hottest TAB pageonFavorite(A hollow star when uncollected; Collected words are solid stars).

Here’s a look at how these two functions are called back inside the RespositoryCell component:

render(){

        let item = this.props.projectModel.item?this.props.projectModel.item:this.props.projectModel;

        let favoriteButton = <TouchableOpacity{/* Call the click favorite callback function */}onPress={()= >this.onPressFavorite()}
        >
            <Image
                style={[styles.favoriteImageStyle,this.props.theme.styles.tabBarSelectedIcon]}
                source={this.state.favoriteIcon}
            />
        </TouchableOpacity>

        return(
            <TouchableOpacity{/ * clickcell*/}onPress={this.props.onSelect}
                 style={styles.container}
            >
               <View style={styles.cellContainerViewStyle}>. {favoriteButton}</View>
            </TouchableOpacity>) } onPressFavorite(){ this.setFavoriteState(! this.state.isFavorite); / / click on the collection of the callback function enclosing props. OnFavorite (this. Props. ProjectModel. Item,! this.state.isFavorite) }Copy the code

As we know from the previous section, when the parent component passes a value to the props of the child component, the corresponding props of the child component is assigned. In this RespositoryCell component is this.props. OnSelect and this.props. These two functions are assigned to the onPress of the two TouchableOpacity components. The ()=> here can be interpreted as a transfer event, indicating the event after the control is clicked.

The difference is that this.props. OnFavorite () can pass two values back to its parent component. Careful students will notice that there are two return values when passing a value to the RespositoryCell.

Note that TouchableOpacity here is similar to TouchableHighlight, which allows non-clickable components to become clickable. The difference is that when used with TouchableOpacity, there is no highlighting when clicked. TouchableHighlight is highlighted by default.

OK, now we know how the parent and child components pass data and events:

  • Parent component to child component: by assigning values directly to properties
  • Child to parent: Callbacks are passed from parent to child

It is important to note that the above are all directly related to the parent components, in fact, there are indirect components, that is, two components are connected to one or more components, such as the child of the parent component of the child component of the child component. Communication between these components can be achieved in the same way as described above, with the difference of how many layers are crossed.

It is important to note that the communication between parent and child components includes not only the direct relationship, but also the indirect relationship, and the component of the indirect relationship is the relationship between the component and the children of its children.

So no matter how many components are separated, as long as the components exist in the chain of the relationship, they can pass data and events in the above two ways.

Communication between sibling components

Although it is not the case that the parent creates the parent, several child components (siblings) of the same parent are also indirectly related (with a common parent in between).

So how do two children under the same parent pass data?

The answer is to pass data through the state of the parent component they share

Because we know that the component’s rendering is triggered by the setState method. Thus, if two child components both render themselves using the same state as their parent.

So when one of the children triggers setState, updates the state of the shared parent, and then triggers the render() method of the parent, both children refresh themselves based on the updated state, thus implementing the child’s data transfer.

Now that we have covered communication between components that are directly or indirectly related, let’s look at communication between components that are not:

2.52 Communication between components that are not directly or indirectly related

If two components belong to different chains of relationships that are neither direct nor indirect (for example, two page components under different modules), then communication needs to be achieved through notification mechanisms or local persistence schemes. The notification mechanism is introduced here, but local persistence is covered in a separate section below.

The notification mechanism can be explained by using the favorites feature of this Demo:

First, a brief introduction to the needs of collection:

  1. If you click the “Favorites” button on the hottest TAB or language trends page, the items will be added to the “Favorites” page (note that there is no network request after clicking the “Favorites” button, that is, there is no network request on the “Favorites” page).
  2. And if you unfavorate in the favorites page, you need to update the effect of unfavorate in the hottest TAB page or the corresponding item in the language trend page (again, no network request).

Because these three pages belong to different modules and do not refresh the list as a network request, you need to use notifications or local storage to meet the above requirements.

In this Demo, the first requirement uses a local persistence scheme, and the second requirement uses a notification mechanism. I’ll cover local persistence separately in the next section. In this section, WE’ll talk about how to use notification in React Native:

There is a special component in React Native that is responsible for notifying this function. Its name is DeviceEventEmitter, which is a built-in component of React Native and can be directly imported into the project. The import works the same as the other built-in components:


import React, { Component } from 'react';
import {
    StyleSheet,
    Text,
    View,
    Image,
    DeviceEventEmitter,
    TouchableOpacity
} from 'react-native';
Copy the code

Since it is a notification, there is a receiving side and a sending side, and both components need to import the notification component.

A notification needs to be registered on the receiving side:

In this Demo, for example, if you change the status of your favorites page, you will send a notification to the hottest TAB page. Therefore, you need to register a notification on the hottest TAB page first. After registering the notification, you can ensure that you can receive the notification on a certain channel in the future

componentDidMount() {
    ...
    this.listener = DeviceEventEmitter.addListener('favoriteChanged_popular', () = > {this.isFavoriteChanged = true; })}Copy the code

Here we register the notification by passing two parameters to the addListener method of DeviceEventEmitter:

  • The first parameter is the channel of the notification to distinguish it from other notifications.
  • The second argument is the function that needs to be called: in this case, only thethis.isFavoriteChangedAssign the value YES. Its purpose is to rerender the interface in the future if the value is equal to YES, updating the favorites state.

Note that there is a register, there is a logout, before the component is uninstalled, the listener needs to be uninstalled:

componentWillUnmount() {
     if(this.listener){
         this.listener.remove(); }}Copy the code

Now that we’ve registered our notifications, we can send them anywhere in the program. In this requirement, we need to block clicking on the item’s favorites button in the Favorites page and send a notification that the status of the favorite TAB has changed:

onFavorite(item,isFavorite){

    ...
    DeviceEventEmitter.emit('favoriteChanged_popular');

}
Copy the code

In this case, the favorites button is blocked. Remember? The onFavorite() function is a callback to the favorite button.

We sent the notification here, just passing in the channel name.

Isn’t it easy?

OK, now that we’re done with communication between components, let’s briefly think about communication schemes between components of various relationships.

Here’s how local persistence works in React Native.

2.6 Local Persistence

Similar to NSUserDefault in iOS, AsyncStorage is a key-value storage system in React Native that can be used for local persistence.

Let’s start with its main interfaces:

2.61 AsyncStorage Common Interface

Retrieves the value based on the key, and the result is placed in the callback function:

static getItem(key: string, callback:(error, result))
Copy the code

Set values by key:

static setItem(key: string, value: string, callback:(error))
Copy the code

Remove items by key:

static removeItem(key: string, callback:(error))
Copy the code

Get all keys:

static getAllKeys(callback:(error, keys))
Copy the code

KeyValuePairs is a two-dimensional array of strings, for example: [[‘k1’, ‘val1’], [‘k2’, ‘val2’]] :

static multiSet(keyValuePairs, callback:(errors))
Copy the code

[‘k1’, ‘k2’] :

static multiGet(keys, callback:(errors, result))
Copy the code

[‘k1’, ‘k2’] : [‘k1’, ‘k2’] :

static multiRemove(keys, callback:(errors))
Copy the code

Clear all items:

static clear(callback:(error))
Copy the code

2.62 Precautions for Using AsyncStorage

Note that when AsyncStorage is used, arrays or dictionaries passed in setItem need to be parsed into JSON strings using json.stringtify () :

AsyncStorage.setItem(this.favoriteKey,JSON.stringify(favoriteKeys));
Copy the code

Here,favoriteKeys is an array.

On the other hand, retrieving objects such as arrays or dictionaries in the getItem method requires the json. parse method to parse them into objects:

AsyncStorage.getItem(this.favoriteKey,(error,result)=>{
     if(! error) {var favoriteKeys=[];
          if (result) {
                favoriteKeys=JSON.parse(result); }... }});Copy the code

Here, result is parsed out as an array.

2.7 Network Request

In React Native, the Fetch function is often used to implement network requests. It supports GET and POST requests and returns a Promise object that contains both a correct result and an incorrect result.

Take a look at the POST request made with Fetch:

fetch('http://www.***.cn/v1/friendList', {
          method: 'POST'.headers: { //header
                'token': ' '
            },
          body: JSON.stringify({ / / parameters
                'start': '0'.'limit': '20',
            })
 })
            .then((response) = > response.json()) // Convert response to JSON
            .then((responseData) = > { // Improved JSON above
                 //using responseData
            })
            .catch((error) = > {
                alert('Return error');
            })
Copy the code

In the Fetch function, the first parameter is the request URL, and the second parameter is a dictionary, including the method, request header, request body and other information.

The subsequent then and catch capture the return value of the fetch function: the correct and wrong result of a Promise object, respectively. Notice that there are two THEN’s, and the second THEN takes the result of the first then. What the first THEN does is convert the result of the network request into a JSON object.

So what are Promise objects?

Promise is a solution to asynchronous programming where a Promise object can get a message for an asynchronous operation. It holds the result of some event (usually an asynchronous operation) that will end in the future.

It is divided into three states:

“Resolved” and “Rejected”

Its constructor takes a function as an argument, resolve and reject:

Resolve changes the state of the Promise object from “unfinished” to “successful” and is called when the asynchronous operation succeeds. The resolve function passes the result of the asynchronous operation as an argument. . The Reject function changes the state of the Promise object from “unfinished” to “success” (i.e., from Pending to Rejected), calls it when the asynchronous operation fails, and passes any errors reported by the asynchronous operation as parameters.

Here’s an example:

var promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* Asynchronous operation succeeded */){
    resolve(value);
  } else{ reject(error); }});Copy the code

The resolve and reject results are captured by the.then and.catch results of the accompanying Fetch functions, respectively.

My personal understanding is that if the return value of an asynchronous operation is a Promise object, then we can use.then and.catch to catch the correct and wrong results, respectively.

Look again at the GET request:

fetch(url)
    .then(response= >response.json())
    .then(result= >{
         resolve(result);
     })

     .catch(error= >{
         reject(error)
     })
Copy the code

Since it is only a GET request, there is no need to configure the request body, and since the fetch returns a Promise object, we can use.then and.catch to catch the correct and wrong results.

In the project, we can create an HttpUtils class that encapsulates GET and POST requests. Take a look at a simple encapsulation:


export default class HttpUtls{
    
    static get(url){
        return new Promise((resolve,reject) = >{
            fetch(url)
                .then(response= >response.json())
                .then(result= >{
                    resolve(result);
                })

                .catch(error= >{
                    reject(error)
                })
        })
    }

    static post(url, data) {
        return new Promise((resolve, reject) = >{
            fetch(url,{
                method:'POST'.header: {'Accept':'application/json'.'Content-Type':'application/json',},body:JSON.stringify(data)
            })

                .then(result= >{
                    resolve(result);
                })

                .catch(error= >{
                    reject(error)
                })
        })
    }
}
Copy the code

2.8 Offline Cache

Offline caching technology can take advantage of the Fetch and AsyncStorage implementations mentioned above, with the request URL as the key and the returned result as the value stored in the local data.

Before the next request, check whether the cache exists and whether the cache has expired. If the cache exists and has not expired, the cache is retrieved and immediately returned for processing. Otherwise, proceed with the network request.

And even if there is no network and eventually returns an error, you can take the cached data and return it immediately.

Take a look at how offline caching is implemented in this project:

// Get data
    fetchRespository(url) {

        return new Promise((resolve, reject) = >{

            // Get the local cache first
            this.fetchLocalRespository(url)
                .then((wrapData) = > {
                    // The local cache was successfully obtained
                if (wrapData) {
                    // The cache object exists
                    resolve(wrapData,true);
                } else {
                    // The cache object does not exist, making a network request
                    this.fetchNetRepository(url)

                        // The network request succeeded
                        .then((data) = > {
                            resolve(data);
                        })
                        // The network request failed
                        .catch(e= > {
                            reject(e);
                        })
                }
            }).catch(e= > {
                    // Failed to obtain the local cache, making a network request
                    this.fetchNetRepository(url)

                        // The network request succeeded
                        .then(result= > {
                            resolve(result);
                        })
                        // The network request failed
                        .catch(e= >{ reject(e); })})})}Copy the code

In the above method, there are two methods to get the local cache and the network request.

The first is to try to get the local cache:

    // Get the local cache
    fetchLocalRespository(url){
        return new Promise((resolve,reject) = >{
            // Get local storage
            AsyncStorage.getItem(url, (error, result)=>{
                if(! error){try {
                        // You must parse into objects using parse
                        resolve(JSON.parse(result));
                    }catch (e){
                        // Parsing failedreject(e); }}else {
                    // Failed to get cachereject(error); }})})}Copy the code

Here, the result of the asyncstorage.getitem method can also be wrapped in a Promise object. Therefore, enclosing fetchLocalRespository (url) results can also be. And then, catch caught.

If the local cache fails to be fetched, a network request is invoked:

    fetchNetRepository(url){
        return new  Promise((resolve,reject) = >{
            fetch(url)
                .then(response= >response.json())
                .catch((error) = >{
                    reject(error);
                }).then((responseData) = >{ resolve(responseData); })})}Copy the code

2.9 Theme Change

This Demo has a theme change requirement. After clicking a color on the theme Settings page, the color scheme of the entire app will change:

We only need to modify the theme of the first page of the four modules, because the theme of the second page is passed in from the first page, so as long as the theme of the first page is changed.

However, instead of notifying all four pages to modify their own pages at the same time after selecting a new theme, we should take a more elegant approach to the problem: use a parent class.

Create a new basecommon.js page as the parent of the four pages. In this parent class, you receive notifications of theme changes and update your own theme. In this way, all four pages that inherit it refresh themselves:

Let’s look at the definition of this superclass:

import React, { Component } from 'react';
import {
    DeviceEventEmitter
} from 'react-native';

import {ACTION_HOME} from '.. /pages/Entry/HomePage'

export default class BaseComponent extends Component {

    constructor(props){
        super(props);
        this.state={
            theme:this.props.theme,
        }
    }

    componentDidMount() {
        this.baseListener = DeviceEventEmitter.addListener('ACTION_BASE',(action,parmas)=>this.changeThemeAction(action,parmas));
    }

    // Remove notifications before uninstallation
    componentWillUnmount() {
        if(this.baseListener){
            this.baseListener.remove(); }}// Receive notifications
    changeThemeAction(action,params){
        if (ACTION_HOME.A_THEME === action){
            this.onThemeChange(params); }}/ / update the theme
    onThemeChange(theme){
        if(! theme)return;
        this.setState({
            theme:theme
        })
    }

}
Copy the code

Update theme events on the Update Theme page:

onSelectTheme(themeKey) {

        this.themeDao.save(ThemeFlags[themeKey]);
        this.props.onClose();
        DeviceEventEmitter.emit('ACTION_BASE',ACTION_HOME.A_THEME,ThemeFactory.createTheme(
            ThemeFlags[themeKey]
        ))
    }
Copy the code

2.10 Debugging Functions

We can use the developer tools of the browser to debug the React Native project. We can view data information and method calls by breaking points:

  1. First, click in the iOS emulatorcommand + DAnd then click in the pop-up menuDebug JS Remotely. Then I opened the browser and started debugging.

  1. The browser typically displays the following page and then clickscommand + option + JThe debugging interface of the real life is displayed.

  1. Click on the top oneSourcesThen click on the left sidedebuggerWorker.jsUnder thelocalhost:8081, you can see the directory files. Click on the file you want to debug, the line number column can be broken.

2.11 iOS and Android

React Native requires that the same code runs on two platforms, which objectively have some differences. Therefore, it is necessary to adapt the two platforms when necessary.

For example, the navigation bar: it exists on iOS devices, but not on Android devices. So when customizing the navigation bar, set the height of the navigation bar to different platforms:

import {
    StyleSheet,
    Platform,
} from 'react-native'

const NAV_BAR_HEIGHT_IOS = 44;
const NAV_BAR_HEIGHT_ANDROID = 50;

navBarStyle: {
        flexDirection: 'row'.alignItems: 'center'.justifyContent: 'space-between'.height: Platform.OS === 'ios' ? NAV_BAR_HEIGHT_IOS : NAV_BAR_HEIGHT_ANDROID,
 },
      
Copy the code

The React Native Platform is a built-in Platform differentiation library that can be used directly after introduction.

It is recommended that you open both the iOS and Android emulators for debugging, because some things may be fine on one Platform, but not on the other, so you need to use Platform to distinguish between platforms.

2.12 Organize the project structure

After entering the React Native demo –version 0.44.0 command on the terminal, a project with the React Native version 0.44.0 will be initialized. This initial project contains the iOS and Android project folders directly, which can be opened and compiled using the corresponding IDE.

The structure of the root directory after creating a React Native project looks like this:

You can also enter the react-native run-ios or react-native run-Android command in the root directory to automatically open the emulator and run the project (provided that the appropriate development environment is installed).

However, it is not enough for a complete project only to have files of these categories. It also needs some files of utility classes, model classes, resources, etc. In order to distinguish them well and make the project structure at a glance, it is necessary to organize the project folder and class name. Here is a solution that I have slightly modified the folder name and structure in the tutorial for your reference:

​

3. To summarize

It has taken nearly two months from the initial study of FlexBox layout to the conclusion of this project. Here are some of my feelings during the learning process:

About the Cost of learning

I think this should be the most concerned point of all people who are not in touch with React Native, so I put it in the first place in the summary. Here I take two typical groups for comparison:

  1. People who only know some kind of Native development but don’t have front-end knowledge such as JavaScript.
  2. People who only know front-end knowledge but not any kind of Native development.

For these two groups, learning React Native costs a lot. But the difference is that the cost of learning for these two groups is different at different stages of the whole learning process. How can I put it?

For the first group of people, the layout of the build and the syntax of JavaScript will be a little difficult due to the lack of front-end knowledge. These two points are exactly the stepping stones of React Native learning. Therefore, it will be difficult for such groups to learn React Native at the initial stage, and the learning cost will be high.

How to learn with the video

When learning by video, you must keep up with your ideas. If the lecturer is explaining the code while writing it, you must make clear what the meaning of each line of code is and why you write it this way. Don’t be afraid to waste time and skip it quickly. Pausing to think actually saves time: if you don’t try to understand the code and the lecturer’s thinking, you’ll get less and less understanding later on and waste a lot of time looking back.

So I think it would be better to listen to the lecture first, clear your mind, and then start to write code, which will be more efficient, and there will be fewer problems in the future.

4. Study reference materials

Here are some of the best React Native introduction materials and blogs I have collected in the past one and a half months:

  • React Native Chinese
  • Gary’s technology blog
  • Marno: React Native is a detailed how-to guide for all developers
  • Desert: A complete guide to Flexbox
  • Ruan Yifeng :Flex layout tutorial: Syntax
  • Eight pieces of code to master Promise completely
  • Ruan Yifeng: Promise object
  • Asce1885 :React Native High quality Learning Materials summary
  • World Summit: Great collection of ReactNative learning resources

As the development time of React Native is less than 2 months, it is inevitable that some aspects are not fully understood or misunderstood. We hope students who find problems can make criticisms or offer valuable suggestions


This post has been synchronized to my blog: A look at some of the key technical aspects of React Native development from a hands-on project

Welcome to visit ^^

— — — — — — — — — — — — — — — — — — — — — — — — — — — — on July 17, 2018 update — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Pay attention!!

The author recently opened a personal public account, mainly to share programming, reading notes, thinking articles.

  • Programming articles: including selected technical articles published by the author before, and subsequent technical articles (mainly original), and gradually away from iOS content, will shift the focus to improve the direction of programming ability.
  • Reading notes: Share reading notes on programming, thinking, psychology, and career books.
  • Thinking article: to share the author’s thinking on technology and life.

Because the number of messages released by the official account has a limit, so far not all the selected articles in the past have been published on the official account, and will be released gradually.

And because of the various restrictions of the major blog platform, the back will also be released on the public number of some short and concise, to see the big dry goods article oh ~

Scan the qr code of the official account below and click follow, looking forward to growing with you