A, instructions

Full-text Technology Stack

  • Core library: [email protected]
  • Route Navigation: React-native Navigation
  • Status management: Redux, redux-thunk, redux-saga, and redux-persist
  • Static test: Flow

This article is suitable for students who have some experience with the React family, but are not familiar with configuring an App from scratch, and want to build an App from scratch.

I am this kind of circumstance, the way to participate in the project, has not been controlled feeling, so take advantage of this project to reconstruct this time opportunity, oneself also follow from zero configuration again, and record down, and hope to be able to study together with your classmates, if you have said the wrong place, also hope you pointed out, or have a better improvement way, welcome to contact.

If you have time, it is best to do it by yourself, which is helpful for how to build a real project.

The whole project has been uploaded to Github, students who are lazy and hands-on can clone it directly and follow it, welcome to improve it together. The current preliminary idea is helpful to some students, and if there is time later, it may be improved into a relatively robust RN basic framework, which can be directly clone the development project

The project github repository portal


Only configuration and basic usage descriptions are provided for each library or content

Physical environment: MAC, Xcode

Windows system students can also see, but need to do their own simulator development environment

2. Quickly build an RN App

The React – the native’s official website

If the basic configuration environment of RN is not configured, click the link to the official website to configure it

react-native init ReactNativeNavigationDemo
cd ReactNativeNavigationDemo
react-native run-ios
Copy the code

Since we planned to use react-native Navigation as a Navigation library from the beginning, the name is a bit longer, so let’s choose the one you like

You will see this screen when you succeed



RN automatically integrates Babel, git, and flow configuration files

Route Navigation: React-native Navigation

React Native Navigation


Why use React Native Navigation instead of React Navigation?

It’s currently the only plugin that uses native code to implement Navigator, After using the navigator push/ POP animation, it will be separated from the JS thread and processed by the native UI thread, the screen cutting effect will be as smooth as the original environment, and there will no longer be the stuck effect of navigator screen cutting animation caused by JS thread rendering. The plugin also comes with a built-in implementation of the original Tabbar


If you are good at English, you can just look at the official documents. If you really don’t understand it, you can compare it with my picture below.

IOS requires Xcode, and those who have not done it may feel a little complicated, so I ran through the process and took a screenshot. As for android configuration, the document is very clear, so I will not run it.

1, install,

yarn add react-native-navigation@latestCopy the code

2. Add xcode project files

Figure refers to the path in the file. / node_modules/react – native – navigation/ios/ReactNativeNavigation xcodeproj



3. Add the project files added above to the library



4. Add a path

$(SRCROOT)/.. /node_modules/react-native navigation/ios Remember to set point 5 in the diagram to recursive



5. Modify the ios/[app name]/ appdelegate. m file

Replace the entire file contents with the following code

#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

#import "RCCManager.h"@implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary  *)launchOptions { NSURL *jsCodeLocation;#ifdef DEBUG
  jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
   jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  self.window.backgroundColor = [UIColor whiteColor];
  [[RCCManager sharedInstance] initBridgeWithBundleURL:jsCodeLocation launchOptions:launchOptions];

  return YES;
}

@end
Copy the code

6. Basic use

1. Create several new pages, as shown in the figure

cd src
mkdir home mine popularize
touch home/index.js mine/index.js popularize/index.js
Copy the code



Inside each index.js file is the same structure, very simple

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


type Props = {};
export default class MineHome extends Component<Props> {
  
  render() {
    return( <View> <Text>MineHome</Text> </View> ); }}Copy the code

SRC /index.js register all pages for unified management

import { Navigation } from 'react-native-navigation';

import Home from './home/index';
import PopularizeHome from './popularize/index';
import MineHome from './mine/index'; // Register all pagesexport function registerScreens() {
  Navigation.registerComponent('home',() => home);
  Navigation.registerComponent('popularize',() => popularize);
  Navigation.registerComponent('mine',() => mine);
}
Copy the code

If you want to introduce Redux, pass store and Provider directly here

export functionRegisterScreens (store, the Provider) {Navigation. RegisterComponent ('home',() => PageOne,store,Provider)
}
Copy the code

3. App.js file modifies the startup mode of App and slightly modifies the page style

import { Navigation } from 'react-native-navigation';
import { registerScreens } from './src/screen/index'; // Implement the registration page method registerScreens(); / / start the app Navigation. StartTabBasedApp ({tabs: [{label:'home',
      screen: 'home',
      title: 'home',
      icon: require('./src/assets/home.png'),
    },
    {
      screen: 'popularize',
      title: 'promotion',
      icon: require('./src/assets/add.png'),
      iconInsets: {
        top: 5, 
        left: 0,
        bottom: -5, 
        right: 0
      },
    },
    {
      label: 'mine',
      screen: 'mine',
      title: '我',
      icon: require('./src/assets/mine.png'),
    }
  ],
  appStyle: {
    navBarBackgroundColor: '# 263136'// Top navigation bar background color navBarTextColor:'white'}, tabsStyle: {tabBarButtonColor:'#ccc', / / button at the bottom of the color tabBarSelectedButtonColor:'#08cb6a'// Select state color tabBarBackgroundColor:'#E6E6E6'// Top bar background color}});Copy the code


Start the App and the current interface that the simulator can see



7. Page skipping and passing parameters

Create a new nextpage. js file under the screen/home folder and register the page in SRC /screen/index.js

Navigation.registerComponent('nextPage', () => NextPage, store, Provider);Copy the code



Then add a jump button to the SRC /screen/home/index.js file and pass the props data









State management: Redux

Redux Chinese document

1. Initialization

1, install,

yarn add redux react-reduxCopy the code

2. Directory building

There are two common ways to build directories

First, write the actions and reducer of the same page in the same folder (can be called componentation), as follows



Second, put all actions in one folder and all reducer in one folder for unified management

There are both good and bad ways, and I won’t explore them here. I’ll use the second one here

A fierce operation like a tiger, the first to create a variety of folders and files

cdsrc mkdir action reducer store touch action/index.js reducer/index.js store/index.js touch action/home.js action/mine.js  action/popularize.js touch reducer/home.js reducer/mine.js reducer/popularize.jsCopy the code

After the above commands are typed, the directory structure should look like this. Each page has its own action and Reducer files respectively, but the output is centrally managed by the index.js file



As for the sequence of creating these three pieces of content, theoretically speaking, there should be Store, reducer, and then action

But after writing more, it is more arbitrary, which is easy to write first.

According to my own habits, I like to write something from nothing. For example, if the reducer is to be introduced in the store, I will first write the reducer

import combinedReducer from '.. /reducer'Copy the code

But before I write reducer, it seems that I need to introduce actions first, so I may go to write actions first

I’m not going to talk about the correct order of writing, but I’m going to follow my habit for the moment

3, the action

I like centralized management mode, so I will centralize all antion index.js file as the total export

All action-type constants are defined here

/ / home pageexport const HOME_ADD = 'HOME_ADD';
export const HOME_CUT = 'HOME_CUT'; / / mime pageexport const MINE_ADD = 'MINE_ADD';
export const MINE_CUT = 'MINE_CUT'; / / popularize pageexport const POPULARIZE_ADD = 'POPULARIZE_ADD';
export const POPULARIZE_CUT = 'POPULARIZE_CUT';
Copy the code

Then go to the action. Js file for each of the other pages. I’ll just use the home page as an example

import * as actionTypes from './index';

export function homeAdd(num) {
  return {
    type: actionTypes.HOME_ADD,
    num
  }
}

export function homeCut(num) {
  return {
    type: actionTypes.HOME_CUT,
    num
  }
}
Copy the code

The simplest action object is returned


4, reducer

Write a reducer on the home page and open the reducer/home.js file

import * as actionTypes from '.. /action/index'; // Const initState = {initCount: 0, name:' ',
  age: ' ',
  job: ' '
}

export default function count(state = initState, action) {
  switch (action.type) {
    case actionTypes.HOME_ADD:
      return{... state, ... action.initCount: }case actionTypes.HOME_CUT: 
      return{... state, ... action.initCount } default:returnstate; }}Copy the code

Then merge all the sub-reducer pages into reducer/index.js file for centralized output

import homeReducer from './home';
import popularizeReducer from './popularize';
import mineReducer from './mine';

const combineReducers = {
  home: homeReducer,
  popularize: popularizeReducer,
  mine: mineReducer
}

export default combineReducersCopy the code


5. Create store

After the reducer is created, open the store/index.js file

import {createStore } from 'redux';

import combineReducers from '.. /reducer/index';

const store = createStore(combineReducers)

export default store;
Copy the code

It’s that simple


6. Store injection

For those of you who have used Redux, react-Redux is here to stay, providing Provider and connect methods

The react-native navigation method is similar to the redux method mentioned earlier, but requires each child page to inject store and Provider

SRC /index.js is modified as follows

import { Navigation } from 'react-native-navigation';

import Home from './home/index';
import PopularizeHome from './popularize/index';
import MineHome from './mine/index'; // Register all pagesexport function registerScreens(store, Provider) {
  Navigation.registerComponent('home', () => Home, store, Provider);
  Navigation.registerComponent('popularize', () => PopularizeHome, store, Provider);
  Navigation.registerComponent('mine', () => MineHome, store, Provider);
}
Copy the code

App.js to modify the execution page registration method

import { Provider } from 'react-redux';
import store from './src/store/index'; // Implement the registerScreens(Store, Provider) page method;Copy the code

2. Play Redux

Now for a taste of redux, open the SRC /screen/home/index.js file

Import two methods

import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
Copy the code

Import the action

import * as homeActions from '.. /.. /action/home';
Copy the code

Define two methods and connect them together

function mapStateToProps(state) {
  return {
    home: state.home
  };
}

function mapDispatchToProps(dispatch) {
  return {
    homeActions: bindActionCreators(homeActions, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(Home);
Copy the code

Now print initCount on the page to see if any component that is connected and any child page that is pushed from that component can get data through this.props

SRC /screen/home/index.js the complete code is shown below

import React, { Component } from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as homeActions from '.. /.. /action/home';

type Props = {};
class Home extends Component<Props> {
  render() {
    return (
      <View style={styles.container}>
        <Text>Home</Text>
        <Text>initCount: {this.props.home.initCount}</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    backgroundColor: '#ccc',
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'}})function mapStateToProps(state) {
  return {
    home: state.home
  };
}

function mapDispatchToProps(dispatch) {
  return {
    homeActions: bindActionCreators(homeActions, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(Home);
Copy the code

From the page, you can see that you have read the data in the state tree, initCount is 0




Let’s try action addition and subtraction again

SRC /screen/home/index.js the complete code is shown below

import React, { Component } from 'react';
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as homeActions from '.. /.. /action/home';

type Props = {};
class Home extends Component<Props> {
  render() {
    return( <View style={styles.container}> <Text>Home</Text> <Text>initCount: {this.props.home.initCount}</Text> <TouchableOpacity style={styles.addBtn} onPress={() => { this.props.homeActions.homeAdd({ initCount: this.props.home.initCount + 2 }); }} > <Text style={styles.btnText}> add 2</Text> </TouchableOpacity> <TouchableOpacity style={styles.cutBtn} onPress={() => {  this.props.homeActions.homeCut({ initCount: this.props.home.initCount - 2 }); }} > <Text style={styles.btnText}> minus 2</Text> </TouchableOpacity> </View>); } } const styles = StyleSheet.create({ container: { backgroundColor:'#ccc',
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  },
  addBtn: {
    backgroundColor: 'green',
    marginVertical: 20,
    width: 200,
    height: 59,
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 10
  },
  cutBtn: {
    backgroundColor: 'red',
    width: 200,
    height: 59,
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 10
  },
  btnText: {
    fontSize: 18,
    color: 'white'}});function mapStateToProps(state) {
  return {
    home: state.home
  };
}

function mapDispatchToProps(dispatch) {
  return {
    homeActions: bindActionCreators(homeActions, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(Home);
Copy the code

Clicking on both buttons should now give you feedback



Now let’s verify the next thing, after this page changes the state in store, the other page mine will be synchronized, that is, whether the global data is shared

The SRC /mine/index.js file is modified as follows

import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as homeActions from '.. /.. /action/home';

type Props = {};
class MineHome extends Component<Props> {
  
  render() {
    return( <View> <Text>initCount: {this.props.home.initCount}</Text> </View> ); }}function mapStateToProps(state) {
  return {
    home: state.home
  };
}

function mapDispatchToProps(dispatch) {
  return {
    homeActions: bindActionCreators(homeActions, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(MineHome);
Copy the code

In this on the page to read the same data. Props. Home. The initCount, then add and subtract data on the first page is the home, then look at mine page, will find initCount synchronous change Namely: we have been in the state management


Isn’t it fun to be here? Redux is a little convoluted, but if you follow it down, you should have a certain outline


5. State tracking: Redux-Logger

At this point, we find that although the state is shared, there is currently no way to keep track of the state and how it changes with each step.

But you can’t manually print the status to the console every time, can you?

It’s time for Redux – Logger

It looks like this and automatically outputs to the console the status before and after each action



Specific use to see the official document, very simple, directly on the code

redux-logger

The installation

yarn add redux-logger
Copy the code

As a middleware, please refer to the Redux Chinese documentation for the usage of middleware

The store/index.js file is modified as follows

import { createStore, applyMiddleware } from 'redux';

import combineReducers from '.. /reducer/index';
import logger from 'redux-logger';

const store = createStore(combineReducers, applyMiddleware(logger));

export default store;
Copy the code

Command +R refresh the emulator and hit +2 to see if the console looks like this.



The console will print it out automatically every time you send an action.


Asynchronous management: redux-thunk

Basic understanding of

Redux-thunk Github Redux-thunk github

Starting point: You want the component to be insensitive to synchronous or asynchronous actions, and you don’t need to explicitly pass in dispatch when calling an asynchronous action

By using the specified Middleware, action creation functions can return functions in addition to action objects. The action creation function becomes thunk

When an action creation function returns a function, this function is executed by Redux Thunk Middleware. This function doesn’t have to be pure; It can also have side effects, including performing asynchronous API requests. This function can also dispatch actions, just like the synchronous actions defined before Dispatch. > One advantage of thunk is that its results can be dispatched again


The installation

yarn add redux-thunk
Copy the code


Into the store

As a middleware, it is used in the same way as the logger above, stroe/index.js can be imported directly

import thunk from 'redux-thunk';
middleware.push(thunk);
Copy the code


use

The action/home.js file is modified as follows

import post from '.. /utils/fetch';

export function getSomeData() {
  return dispatch => {
    post('/get/data',{}, res => {
      const someData = res.data.someData;
      dispatch({
        type: actionTypes.HOME_GET_SOMEDATA,
        someData
      })
    })
  }
}Copy the code


Off-topic: Encapsulate the request function POST

Insert a little line here about wrapping the request function POST (the following is a condensed version, keeping only the core idea)

cd src
mkdir utils
touch utils/fetch.js
Copy the code

Common methods and functions are encapsulated in the utils folder

The utils/fetch. Js file is as follows

export default functionPost (URL, data, sucCB, errCB) {// Domain name, body, header, etc'www.host.com';
  const requestUrl = `${host}/${url}`;
  const body = {};
  const headers = {
    'Content-Type': 'application/json'.'User-Agent': ' '}; Fetch (requestUrl, {method:'POST',
    headers: headers,
    body: body
  }).then(res => {
    if (res && res.status === 200) {
      return res.json();
    } else {
      throw new Error('server'); }}). Then (res => {//if(res && res.code === 200 && res.enmsg === 'ok') {sucCB(res); }else{// Failed callback errCB(res); }}). Catch (err => {// handle error})}Copy the code



Asynchronous management: Redux-saga

Basic concepts please move on

Readme | story – the saga of Chinese documents

Starting point: The imperative thunk has limited expressiveness for complex asynchronous data flows that need to be expressed declaratively (long flow forms, retry after a failed request, etc.)


The installation

yarn add redux-sagaCopy the code

Create saga file

The create sequence is somewhat like a reducer — we create saga-related folders and files first, and then inject them into the store

cd src
mkdir saga
touch saga/index.js saga/home.js saga/popularize.js saga/mine.jsCopy the code


Start by modifying the saga/home.js file

import { put, call, takeLatest } from 'redux-saga/effects';

import * as actionTypes from '.. /action/index';
import * as homeActions from '.. /action/home';
import * as mineActions from '.. /action/mine';

import post from '.. /utils/fetch';

function getSomeThing() {
  post('/someData', {}, res => {}, err => {}); } // The request methods in this function are written off the top of the page, without introducing real API.function* getUserInfo({ sucCb, errCB }) { try { const res = yield call(getSomeThing()); const data = res.data; yield put(homeActions.getSomeData()) yield put(homeActions.setSomeData(data)) yield call(sucCb); } catch (err) { yield call(errCB, err); }}export const homeSagas = [
  takeLatest(actionTypes.HOME_GET_SOMEDATA, getUserInfo)
]
Copy the code

Saga/mime. Js file

export const mineSagas = []
Copy the code

Saga/popularize. Js file

export const popularizeSagas = []
Copy the code


The saga/index.js file, as the total export, is modified as follows

import { all } from 'redux-saga/effects';

import { homeSagas } from './home';
import { mineSagas } from './mine';
import { popularizeSagas } from './popularize';

export default function* rootSaga() {
  yield all([...homeSagas, ...mineSagas, ...popularizeSagas]);
}
Copy the code


Inject Saga into the store

The store/index.js file is modified

import createSagaMiddleware from 'redux-saga';
import rootSaga from '.. /saga/index'; Const sagaMiddleware = createSagaMiddleware(rootSaga); // Generate saga middleware const sagaMiddleware = createSagaMiddleware(rootSaga); middleware.push(sagaMiddleware);Copy the code


Data persistence: redux-persist

GitHub – rt2zz/redux-persist: persist and rehydrate a redux store

As the name implies, data persistence is generally used to store data such as login information that needs to be kept locally

Since the data in the Store will be returned to the initial state of initState in the Reducer after each reopening of the app, data such as login information needs persistent storage.

RN’s built-in AsyncStorage can do this, but it is cumbersome to use and cannot be injected into the store to achieve uniform state management, so redux-persist is introduced

The installation

yarn add redux-persistCopy the code

Into the store

The complete code for the store/index.js file is as follows

import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import createSagaMiddleware from 'redux-saga';
import { persistStore, persistCombineReducers } from 'redux-persist';
import storage from 'redux-persist/es/storage';

import combineReducers from '.. /reducer/index';
import rootSaga from '.. /saga/index';

const persistConfig = {
  key: 'root', storage, // Whitelist: only mine data is persisted whitelist: ['mine']}; Persistent Reducer = persistCombineReducers(persistConfig, combineReducers); const sagaMiddleware = createSagaMiddleware(); / / middleware const createStoreWithMiddleware = applyMiddleware (thunk, sagaMiddleware, logger) (createStore); const configuerStore = onComplete => {let store = createStoreWithMiddleware(persistReducer);
  let persistor = persistStore(store, null, onComplete);
  sagaMiddleware.run(rootSaga);
  return { persistor, store };
};

export default configuerStore;
Copy the code


This place, instead of treating middleware as an array, writes directly to the applyMiddleware method

Instead of exporting stores directly, a function called configuerStore generates stores everywhere.

The introduction of the app.js file also needs to be modified a bit

import configuerStore from './src/store/index';
const { store } = configuerStore(() => { });Copy the code


Nine: static test: Flow

React Native: Flow



Ten, the latter


So far, we’ve introduced redux-Logger, redux-Thunk, redux-Saga, and redux-persist

The core development code base has been configured

The project has been uploaded to Github. Welcome star

The project github repository portal


Then there are ancillary configurations that can be used at development time, such as Flow, Babel (already configured when RN is initialized), Eslint, and so on

In addition, since it is an App, the ultimate goal is of course to be put on the App Store and various Android markets, and may share some content about Aurora push jPush, hot update CodePush, packaging upload audit and so on.


Thank you for your patience to see here, hope to gain!

If you are not very busy, please click star⭐ [Github blog portal], which is a great encouragement to the author.

I like to keep records during the learning process and share my accumulation and thinking on the road of front-end. I hope to communicate and make progress with you. For more articles, please refer to [amandakelake’s Github blog].