The demo address:https://github.com/recall-lidemin/baseapp.git

The demo is not perfect. The article is mainly written according to the project I am doing now. I will improve the demo sometime

1. Environment preparation

Before the start of the app development, we need to build the app development environment, including ios and android, the construction of the concrete steps can consult the official website of reactnative. Cn/docs/gettin…

All the third-party packages in the following are from the website js.coach/, and you can refer to the website for ios and Android configuration of all relevant packages. The specific configuration is not explained in this article, but you need to learn to read the documents when writing projects. This article is only a basic introduction, and the configuration of specific packages can follow the guidance and documentation.

If you use expo scaffolding to build your app, it will be much easier, it will help you integrate, but not as flexible as using native RN, some places expo may not work and some third party packages will not work

2. Initialize the project

Once you have configured your local environment, you can start your App project creation and development

2.1. Run the following command to initialize the project

  • npx react-native init MyApp --template react-native-template-typescript
  • This command is initialized with the nameMyAppProject, and use TS template, this TS must be added, this year, the project who also does not add a TS
  • Ts learning, search a video, a look on the line, interview, honest to memorize it, write projects, on the point of the real line

2.2. Configure multiple environments

  • Use the react-native config package in thehttps://js.coach/Search through the website
  • Install the packageyarn add react-native-config
  • Configure ios and Android based on the package document
  • After the configuration, create it in the root directory of the project.envfile.env.devfile.env.testfile.env.prodfile
  • configurationpackage.jsonStart and build scripts
"scripts": {
    "android": "cp -f .env.dev .env && react-native run-android"."ios": "cp -f .env.dev .env && react-native run-ios"."start": "cp -f .env.dev .env && react-native start"."build": "cp -f .env.prod .env && react-native start"."test": "cp -f .env.test .env && react-native start"."build:prod": "cp -f .env.prod .env && cd android && ./gradlew app:assembleRelease"."build:test": "cp -f .env.test .env && cd android && ./gradlew app:assembleRelease"."lint": "eslint . --ext .js,.jsx,.ts,.tsx"."lint-staged": "lint-staged"."lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx "."icon": "npx iconfont-rn"
  },
Copy the code
  • cp -f .env.dev .envThis is to copy the configuration of different environments to.envFile to ensure that different commands are executed and different environment variables are distinguished
  • Use also refer to the documentation

2.3. Configure an absolute path

  • Use the babel-plugin-module-resolver plug-in
  • The installationyarn add babel-plugin-module-resolver
  • inbabel.config.jsAdd the following code to the file
plugins: [
    [
      'module-resolver',
      {
        root: ['./src'].alias: {
          The '@': './src'}}]]Copy the code
  • intsconfig.jsonIn the configuration
"baseUrl": "./src"."paths": {
   "@ / *": ["*"]}Copy the code

3. Configure the navigator

Use the react – navigation, is now at version 5.0, version 5.0 of previous package split, every navigator split into separate packages, specific use our document reference website https://reactnavigation.org/ can be configured

3.1. Install the navigator core package and dependency package

  • The installationyarn add @react-navigation/native
  • Because this library relies on other libraries, native RN applications are instructed to install dependent libraries
yarn add react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view
Copy the code
  • From React Native 0.60 and higher, linking is automatic. So you don’t need to run react-native link. No need for us to manually link
  • In index.js or app.tsximport 'react-native-gesture-handler';

  • The ios side,Run CD ios && Pod Install

3.2. Install the stack navigator

  • yarn add @react-navigation/stack
  • Create stack navigators in the page. Note that all navigators must be included in the core package deconstructedNavigationContainerIn the container
import React, {FC} from 'react'
import {
  CardStyleInterpolators,
  createStackNavigator,
  HeaderStyleInterpolators,
  StackNavigationProp
} from '@react-navigation/stack'
import Detail from '@/pages/detail'
import {Platform, StyleSheet} from 'react-native'
import BottomTabs from './BottomStackNavigator'

export type RootStackParamList = {
  BottomTabs: { screen? : string }Home: undefined
  Detail: {id: number}
}
export type RootStackNavigation = StackNavigationProp<RootStackParamList>
/** * Create stack Navigator * Navigator route * Screen page */
const Stack = createStackNavigator<RootStackParamList>() 

const RootNavigator: FC = () = > {
  return (
    <Stack.Navigator
      headerMode="float"
      screenOptions={{
        headerStyleInterpolator: HeaderStyleInterpolators.forUIKit.cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS.gestureEnabled: true// Enable android gesturesgestureDirection: 'horizontal', // Gesture direction set levelheaderStyle: {
          . Platform.select({
            android:{// Android title bar styleelevation: 0.borderBottomWidth: StyleSheet.hairlineWidth}})},headerTitleAlign: 'center'
      }}>
      <Stack.Screen name="BottomTabs" component={BottomTabs} />// Create a nested tag navigator<Stack.Screen
        options={{headerTitle:'details'}}name="Detail"
        component={Detail}
      />
    </Stack.Navigator>)}export default RootNavigator
Copy the code

3.3. Install label navigator

  • yarn add @react-navigation/bottom-tabs
  • Create a label navigator
import React, {FC, useEffect} from 'react'
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'
import Home from '@/pages/home'
import Mine from '@/pages/mine'
import {RouteProp, getFocusedRouteNameFromRoute} from '@react-navigation/native'
import {RootStackNavigation, RootStackParamList} from './RootStackNavigation'

export type BottomStackParamList = {
  Home: undefined
  Listen: undefined
  Mine: undefined
}
type Route = RouteProp<RootStackParamList, 'BottomTabs'>
interface IProps {
  navigation: RootStackNavigation
  route: Route
}
const Tab = createBottomTabNavigator<BottomStackParamList>()
const getHeaderTitle = (route: Route) = > {
  constrouteName = getFocusedRouteNameFromRoute(route) || route.params? .screen ||'Home'
  switch (routeName) {
    case 'Home':
      return 'home'
    case 'Mine':
      return 'I'
    default:
      break}}const BottomTabs: FC<IProps> = ({navigation, route}) = > {
  useEffect(() = > {
    navigation.setOptions({
      headerTitle: getHeaderTitle(route)
    })
  }, [navigation, route])
  return (
    <Tab.Navigator
      tabBarOptions={{
        activeTintColor: '#f86442'
      }}>
      <Tab.Screen
        name="Home"
        options={{
          tabBarLabel:'Home'}}component={Home}
      />
      <Tab.Screen
        name="Mine"
        options={{
          tabBarLabel:'Mine'}}component={Mine}
      />
    </Tab.Navigator>)}export default BottomTabs

Copy the code

3.4 Modal navigator

import React, {FC} from 'react'
import {
  createStackNavigator,
  StackNavigationProp,
  TransitionPresets
} from '@react-navigation/stack'
import Login from '@/pages/login'
import RootNavigator from './RootStackNavigation'

export type ModalStackParamList = {
  Login: undefined
  RootNavigator: { screen? : string } }export type ModalStackNavigation = StackNavigationProp<ModalStackParamList>

const ModalStack = createStackNavigator<ModalStackParamList>()

const ModalStackNavigation: FC = () = > {
  return (
    <ModalStack.Navigator
      mode="modal"
      headerMode="screen"
      screenOptions={{
        headerTitleAlign: 'center',
        gestureEnabled: true.. TransitionPresets.ModalSlideFromBottomIOS.headerBackTitleVisible: false.headerShown: false}} >
      <ModalStack.Screen name="Login" component={Login} />
      <ModalStack.Screen name="RootNavigator" component={RootNavigator} />// Nested root stack navigator</ModalStack.Navigator>)}export default ModalStackNavigation

Copy the code

3.5. Navigator nesting

  • The core package is used hereNavigationContainerContainer, wrap up all the navigators,NavigationContainerJust wrap one in the outermost layer
import React, {FC} from 'react'
import {NavigationContainer} from '@react-navigation/native'
import ModalStackNavigation from './ModalStackNavigation' // Introduce the Modal navigator above

const Navigator: FC = () = > {
  return (
    <NavigationContainer>
      <ModalStackNavigation />
    </NavigationContainer>)}export default Navigator

Copy the code

4. Introduce the dva

  • The installationyarn add dva-core-ts dva-loading-ts
  • Start by creating a new model file, models/model.ts
import { Effect, Model } from 'dva-core-ts'
import { Platform } from 'react-native'
import { Reducer } from 'redux'interface CurrentUser {} interface UserState { currentUser? : CurrentUser } interface UserModalextends Model {
  namespace: 'user'
  state: UserState
  reducers: {
    saveCurrentUser: Reducer<UserState>
  }
  effects: {
    fetch: Effect
    getUser: Effect
    clearUser: Effect
  }
}
const userModal: UserModal = {
  namespace: 'user'.state: {
    currentUser: undefined
  },
  effects: {*fetch(_, { call, put }) { 
     // Request logic}},reducers: {
    saveCurrentUser(state, action) {
      return {
        ...state,
        currentUser: action.payload
      }
    }
  }
}

export default userModal

Copy the code
  • Create the repository store/ dvA.ts
import { create } from 'dva-core-ts'
import createLoading from 'dva-loading-ts'
import models from '@/models/model'

// 1. Create a DVA instance
const app = create()
// 2. Load model objects
models.forEach((model) = > {
  app.model(model)
})
app.use(createLoading())
// 3. Start dVA
app.start()
// 4. Export DVA data
export default app._store

Copy the code
  • Registration, introduced in app.tsx, is registered to the top level and all of the following components can be consumed
/* eslint-disable radix */
import React, { useEffect, useRef, useState } from 'react'
import { Provider } from 'react-redux'
import store from '@/store/dva'

const App = () = > {
  return (
    <Provider store={store}>
      <NavigatorBase />
    </Provider>)}export default App

Copy the code

5. Picture selection and photo taking (support multiple selection)

  • yarn add react-native-image-crop-picker

6. Ajax requests

  • yarn add axios

7. Integrated codepush

  • Microsoft provides app hot update, installationreact-native-code-pushCurrently codePush is no longer supported and appCenter is officially recommended to replace it. However, there are still many codePush solutions online, so CODEPush is still used
  • For the react-native code-push integration solution, see git:https://github.com/microsoft/react-native-code-pushThe documentation is very detailed
  • Codepush account creation, APP registration, update push and other commandshttps://docs.microsoft.com/en-us/appcenter/distribution/codepush/cli
  • Modify the app. TSX file
/* eslint-disable radix */
import React, { useEffect, useRef, useState } from 'react'
import { Provider } from 'react-redux'
import store from '@/store/dva'
import 'react-native-gesture-handler'
import NavigatorBase from '@/navigator'
import { AppState, Platform, Text, View } from 'react-native'
import CodePush from 'react-native-code-push'
import { hp, wp } from '@/utils'
import * as Progress from 'react-native-progress'
import { primary_color } from '@/config'
import Config from 'react-native-config'

const App = () = > {
  const [syncMessage, setSyncMessage] = useState<string>()
  const [progress, setProgress] = useState<any>()

  const codePushStatusDidChange = (syncStatus: any) = > {
    switch (syncStatus) {
      case CodePush.SyncStatus.CHECKING_FOR_UPDATE:
        setSyncMessage('Check for updates... ')
        break
      case CodePush.SyncStatus.DOWNLOADING_PACKAGE:
        setSyncMessage('Downloading update:')
        break
      case CodePush.SyncStatus.AWAITING_USER_ACTION:
        setSyncMessage('Waiting')
        break
      case CodePush.SyncStatus.INSTALLING_UPDATE:
        setSyncMessage('In installation')
        break
      case CodePush.SyncStatus.UP_TO_DATE:
        setSyncMessage('Current version')
        setProgress(false)
        break
      case CodePush.SyncStatus.UPDATE_IGNORED:
        setSyncMessage('Update cancelled')
        setProgress(false)
        break
      case CodePush.SyncStatus.UPDATE_INSTALLED:
        setSyncMessage('Restart to apply the update')
        setProgress(false)
        CodePush.allowRestart()
        CodePush.restartApp()
        break
      case CodePush.SyncStatus.UNKNOWN_ERROR:
        setSyncMessage('Unknown error')
        setProgress(false)
        break}}const codePushDownloadDidProgress = (pro: any) = > {
    setProgress(pro)
  }
  // code-push
  const syncImmediate = () = > {
    CodePush.sync(
      {
        // Install mode
        //ON_NEXT_RESUME the next time it returns to the foreground
        //ON_NEXT_RESTART The next restart
        //IMMEDIATE updates immediately
        mandatoryInstallMode: CodePush.InstallMode.IMMEDIATE, ... Platform.select({ios: {
            deploymentKey: Config.IOS_KEY
          },
          android: {
            deploymentKey: Config.ANDROID_KEY
          }
        }),
        / / dialog
        updateDialog: {
          // Whether to display the update description
          appendReleaseDescription: true.// Update the description prefix. The default is "Description"
          descriptionPrefix: 'Update:'.// Forces the button text to be updated. The default is continue
          mandatoryContinueButtonLabel: 'Update now'.// Mandatory update information. The default is "An update is available that must be installed."
          mandatoryUpdateMessage: 'Must be updated before use'.// The text of the button is "ignore" by default.
          optionalIgnoreButtonLabel: 'ignore'.// Confirm button text when not forced to update. The default is "Install"
          optionalInstallButtonLabel: 'Update now'.// The updated message text is checked when the update is not mandatory
          optionalUpdateMessage: ' '.// Title of the Alert window
          title: 'Version Update'
        }
      },
      codePushStatusDidChange,
      codePushDownloadDidProgress
    )
  }
  CodePush.disallowRestart() // Disable restart
  syncImmediate() // Start checking for updates

  let progressView
  if (progress) {
    progressView = (
      <>
        <View
          style={{
            position: 'absolute',
            left: wp(5),
            top: hp(25),
            width: wp(90),
            height: 80.backgroundColor: '#eee',
            zIndex: 10.alignItems: 'center',
            borderRadius: 5}} >
          <Text style={{ marginVertical: 15.fontSize: 16}} >Version update</Text>
          <View
            style={{
              flexDirection: 'row',
              alignItems: 'center',
              justifyContent: 'center'
            }}>
            <Text
              style={{
                fontSize: 16.color: '#333',
                alignItems: 'center',
                justifyContent: 'center'
              }}>
              {syncMessage}
            </Text>
            <Progress.Bar
              color={primary_color}
              progress={progress.receivedBytes / progress.totalBytes}
              width={wp(50)}
              height={16}
              borderRadius={8}
            />
            <Text style={{ marginHorizontal: 5}} >
              {parseInt(
                (
                  (progress.receivedBytes / progress.totalBytes) *
                  100
                ).toString()
              )}
              %
            </Text>
          </View>
        </View>
      </>)}return (
    <Provider store={store}>
      <NavigatorBase />
      {progressView}
    </Provider>)}let codePushOptions = {
  // Set the frequency of checking for updates
  // when the ON_APP_RESUME APP returns to the foreground
  //ON_APP_START when the APP is started
  //MANUAL MANUAL check
  checkFrequency: CodePush.CheckFrequency.ON_APP_RESUME
}

export default CodePush(codePushOptions)(App)

Copy the code

8. Integration push, depending on which third party we cooperate with, we use aurora