• 1

  • 487

  • 收藏

使用React-Native搭建自己的App

1个月前

demo地址:https://github.com/recall-lidemin/baseapp.git

demo不完善,文章主要是根据现在在做的项目写的,抽空会完善demo

1.环境准备

在app开发开始之前,我们需要搭建app开发环境,包括ios端和android端,具体的搭建步骤可以参照官方网站reactnative.cn/docs/gettin…

下文中所有的第三方包均来自 js.coach/ 这个网站,所有相关包的ios和android配置,均可参考该网站,具体配置不再本文具体讲解,面试要会背题,但是写项目你要学会看文档,本文只做基础介绍,具体包的配置等跟着指引以及文档走即可。

如果你使用expo脚手架搭建app,那将变得更加简单,一切帮你集成好了,但灵活度上不如使用原生RN,有些地方expo可能无法实现,一些第三方包也无法使用

2.项目初始化

当你配置完成了自己的本地环境以后,就可以正式开始进行App项目创建和开发了

2.1、执行以下命令初始化项目

  • npx react-native init MyApp --template react-native-template-typescript
  • 这条命令是初始化名叫 MyApp 的项目,并且使用ts模板,这个ts一定要加上,这年头,起个项目谁还不加个ts
  • ts的学习,搜个视频,一看就行,面试的话,老老实实去背去记吧,写项目,就整点实在的就行

2.2、配置多环境

  • 使用react-native-config这个包,在该https://js.coach/网站中搜索
  • 安装这个包 yarn add react-native-config
  • 按照包包文档介绍,配置ios和android
  • 配置完成后,在项目根目录建立 .env 文件.env.dev 文件.env.test 文件.env.prod 文件
  • 配置 package.json 启动和构建脚本
"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"
  },
复制代码
  • cp -f .env.dev .env 这是把不同环境的配置复制到 .env 文件,保证执行不同命令,区分不同环境变量
  • 使用也参照文档即可

2.3、配置绝对路径

  • 使用 babel-plugin-module-resolver 插件
  • 安装 yarn add babel-plugin-module-resolver
  • babel.config.js文件中新增以下代码
plugins: [
    [
      'module-resolver',
      {
        root: ['./src'],
        alias: {
          '@': './src'
        }
      }
    ]
  ]
复制代码
  • tsconfig.json中配置
"baseUrl": "./src",
"paths": {
   "@/*": ["*"]
}
复制代码

3.配置导航器

使用react-navigation,现在是5.0版本,5.0版本对之前的包进行了拆分,各个导航器都拆到了单独的包中,具体使用我们参照官网文档 https://reactnavigation.org/ 进行配置即可

3.1、安装导航器核心包和依赖包

  • 安装堆栈式导航器 yarn add @react-navigation/native
  • 因为这个库依赖其他一些库,所以按照官方指导,原生rn应用安装依赖库
yarn add react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view
复制代码
  • From React Native 0.60 and higher, linking is automatic. So you don't need to run react-native link,rn 0.6以后的版本都是自动link,无需我们手动link
  • 在 index.js或者App.tsx中引入 import 'react-native-gesture-handler';

  • ios端,执行 cd ios && pod install

3.2、安装堆栈式导航器

  • yarn add @react-navigation/stack
  • 在页面中创建堆栈式导航器,注意,所有导航器必须包含在核心包解构出来的 NavigationContainer 容器中
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>
/**
 * 创建堆栈式导航器
 * Navigator 路由
 * Screen 页面
 */
const Stack = createStackNavigator<RootStackParamList>() 

const RootNavigator: FC = () => {
  return (
    <Stack.Navigator
      headerMode="float"
      screenOptions={{
        headerStyleInterpolator: HeaderStyleInterpolators.forUIKit,
        cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS,
        gestureEnabled: true, // 开启安卓手势
        gestureDirection: 'horizontal', // 手势方向设置水平
        headerStyle: {
          ...Platform.select({
            android: {
              // 安卓标题栏样式
              elevation: 0,
              borderBottomWidth: StyleSheet.hairlineWidth
            }
          })
        },
        headerTitleAlign: 'center'
      }}>
      <Stack.Screen name="BottomTabs" component={BottomTabs} /> // 嵌套标签导航器
      <Stack.Screen
        options={{headerTitle: '详情'}}
        name="Detail"
        component={Detail}
      />
    </Stack.Navigator>
  )
}

export default RootNavigator
复制代码

3.3、安装标签导航器

  • yarn add @react-navigation/bottom-tabs
  • 创建标签导航器
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) => {
  const routeName =
    getFocusedRouteNameFromRoute(route) || route.params?.screen || 'Home'
  switch (routeName) {
    case 'Home':
      return '首页'
    case 'Mine':
      return '我的'
    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: '首页'
        }}
        component={Home}
      />
      <Tab.Screen
        name="Mine"
        options={{
          tabBarLabel: '我的'
        }}
        component={Mine}
      />
    </Tab.Navigator>
  )
}

export default BottomTabs

复制代码

3.4、model导航器

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} /> // 嵌套根堆栈式导航器
    </ModalStack.Navigator>
  )
}

export default ModalStackNavigation

复制代码

3.5、导航器嵌套

  • 这里使用核心包中的NavigationContainer容器,把所有导航器包起来,NavigationContainer只要在最外层包一个即可
import React, {FC} from 'react'
import {NavigationContainer} from '@react-navigation/native'
import ModalStackNavigation from './ModalStackNavigation' // 引入上面的Modal导航器

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

export default Navigator

复制代码

4.引入dva

  • 安装 yarn add dva-core-ts dva-loading-ts
  • 首先要新建一个model文件,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 UserModal extends 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 }) { 
     // 请求逻辑
    }
  },
  reducers: {
    saveCurrentUser(state, action) {
      return {
        ...state,
        currentUser: action.payload
      }
    }
  }
}

export default userModal

复制代码
  • 创建仓库 store/dva.ts
import { create } from 'dva-core-ts'
import createLoading from 'dva-loading-ts'
import models from '@/models/model'

// 1. 创建dva实例
const app = create()
// 2.加载model对象
models.forEach((model) => {
  app.model(model)
})
app.use(createLoading())
// 3.启动dva
app.start()
// 4.导出dva数据
export default app._store

复制代码
  • 注册,在App.tsx中引入,注册到最顶级,下面所有组件就可以消费了
/* 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

复制代码

5.图片选择,拍照(支持多选)

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

6.ajax请求

  • yarn add axios

7.集成codepush

  • 微软提供的app热更新,安装 react-native-code-push,目前codepush已经不再支持,官方推荐使用appcenter替代,但是网上还是codepush的解决方案多,所以用的还是codepush
  • react-native-code-push具体集成方案,可以参照git:https://github.com/microsoft/react-native-code-push,文档写的非常详细
  • codepush的账号创建,app注册,更新推送等命令https://docs.microsoft.com/en-us/appcenter/distribution/codepush/cli
  • 修改App.tsx文件
/* 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('检查更新...')
        break
      case CodePush.SyncStatus.DOWNLOADING_PACKAGE:
        setSyncMessage('下载更新中:')
        break
      case CodePush.SyncStatus.AWAITING_USER_ACTION:
        setSyncMessage('等待中')
        break
      case CodePush.SyncStatus.INSTALLING_UPDATE:
        setSyncMessage('安装中')
        break
      case CodePush.SyncStatus.UP_TO_DATE:
        setSyncMessage('已是最新版本')
        setProgress(false)
        break
      case CodePush.SyncStatus.UPDATE_IGNORED:
        setSyncMessage('更新已取消')
        setProgress(false)
        break
      case CodePush.SyncStatus.UPDATE_INSTALLED:
        setSyncMessage('重启以应用更新')
        setProgress(false)
        CodePush.allowRestart()
        CodePush.restartApp()
        break
      case CodePush.SyncStatus.UNKNOWN_ERROR:
        setSyncMessage('未知的错误')
        setProgress(false)
        break
    }
  }
  const codePushDownloadDidProgress = (pro: any) => {
    setProgress(pro)
  }
  // code-push
  const syncImmediate = () => {
    CodePush.sync(
      {
        //安装模式
        //ON_NEXT_RESUME 下次恢复到前台时
        //ON_NEXT_RESTART 下一次重启时
        //IMMEDIATE 马上更新
        mandatoryInstallMode: CodePush.InstallMode.IMMEDIATE,
        ...Platform.select({
          ios: {
            deploymentKey: Config.IOS_KEY
          },
          android: {
            deploymentKey: Config.ANDROID_KEY
          }
        }),
        //对话框
        updateDialog: {
          //是否显示更新描述
          appendReleaseDescription: true,
          //更新描述的前缀。 默认为"Description"
          descriptionPrefix: '更新内容:',
          //强制更新按钮文字,默认为continue
          mandatoryContinueButtonLabel: '立即更新',
          //强制更新时的信息. 默认为"An update is available that must be installed."
          mandatoryUpdateMessage: '必须更新后才能使用',
          //非强制更新时,按钮文字,默认为"ignore"
          optionalIgnoreButtonLabel: '忽略',
          //非强制更新时,确认按钮文字. 默认为"Install"
          optionalInstallButtonLabel: '立即更新',
          //非强制更新时,检查到更新的消息文本
          optionalUpdateMessage: '',
          //Alert窗口的标题
          title: '版本更新'
        }
      },
      codePushStatusDidChange,
      codePushDownloadDidProgress
    )
  }
  CodePush.disallowRestart() //禁止重启
  syncImmediate() //开始检查更新

  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 }}>版本更新</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 = {
  //设置检查更新的频率
  //ON_APP_RESUME APP恢复到前台的时候
  //ON_APP_START APP开启的时候
  //MANUAL 手动检查
  checkFrequency: CodePush.CheckFrequency.ON_APP_RESUME
}

export default CodePush(codePushOptions)(App)

复制代码

8.集成推送,看与哪家第三方合作了,我们用的极光

免责声明:文章版权归原作者所有,其内容与观点不代表Unitimes立场,亦不构成任何投资意见或建议。

ios

487

相关文章推荐

未登录头像

暂无评论