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立场,亦不构成任何投资意见或建议。
暂无评论