内容简介:循例都要介绍下React-Native,下面简称RN。 RN是让你使用Javascript编写的原生移动应用。它在设计原理上和React一致,通过声明式的组件机制来搭建丰富多彩的用户界面。其实文档上面写得很清楚,很友好的分了开发平台跟目标平台,基本上按着上面做就可以。我用的是自己的小米Note3真机开发的。按着官网的实例一步步做。安装很简单
循例都要介绍下React-Native,下面简称RN。 RN是让你使用Javascript编写的原生移动应用。它在设计原理上和React一致,通过声明式的组件机制来搭建丰富多彩的用户界面。
本文分为以下几点
- 搭建RN环境
- 封装一些公共方法,请求,本地存储
- 使用typescript
- 使用redux状态管理工具
- 使用iconfont
- BackHandler
- 页面效果
- 安卓打包APK
1. 搭建RN环境
- 安装
其实文档上面写得很清楚,很友好的分了开发平台跟目标平台,基本上按着上面做就可以。我用的是自己的小米Note3真机开发的。按着官网的实例一步步做。安装很简单
- 运行
react-native run-android
- 遇到的问题
- 找不到
ANDROID_HOME
环境变量
在当前终端下执行一次 source ~/.bash_profile
,问题解决。
- 成功运行时
- 调试
- react-native log-android
在终端会输出你console.log 出来的数据,不会影响程序的运行速度。
- Debug JS Remotely
摇晃手机弹出开发者选项菜单,选择Debug JS Remotely,浏览器会自动打开调试页面 http://localhost:8081/debugger-ui,Chrome 中并不能直接看到 App 的用户界面结构,而只能提供 console 的输出。
- React Developer Tools
这个插件可以看到页界面的插件布局,以及props等属性,但是这个貌似不能看console.log的内容。
- React Native Debugger 文档
由于我是在真机上面调试,所以需要配置 setupDevtools.js
reactDevTools.connectToDevTools({ isAppActive, host:'你电脑的ip地址', // Read the optional global variable for backward compatibility. // It was added in https://github.com/facebook/react-native/commit/bf2b435322e89d0aeee8792b1c6e04656c2719a0. port: window.__REACT_DEVTOOLS_PORT__, resolveRNStyle: require('flattenStyle'), }); 复制代码
接着,执行上面第二种的操作,打开 Debug JS Remotely
。这个调试比较爽,既有能console.log的也有UI布局的,但是我的会影响程序运行,会有卡顿情况。
- 拔掉数据线开发
如果你不想一直插着数据线,可以通过网络对你的程序进行调试。第一次运行react-native run-android时需要连着数据线,之后可以进入 Dev Settings
-> Debud server host & port for device
填上 [你开发电脑的ip]:8081
,在下一次重新运行的时候可以用 react-native start
运行。
- Hot Reload
局部刷新
- Live Reload
整个应用重新运行一次
2. 目录结构
- api: 相关的功能模块接口放在一个文件下面,例如订车相关的功能就放在aboutBookCar.js里面。
- assets: 放一些图片或者字体等静态资源。
- common: 放公共的方法。
- components: 放置通用组件,这里分功能组件跟UI组件。
- redux: redux相关。
- styles: 公共样式。
- types: ts声明文件。
- views: 放置各个主页面。
3. 各个主要模块
- http模块
// http.js 处理请求,储存token import { AsyncStorage } from 'react-native' import { login } from '../api/login' import axios from 'axios' async function checkStatus(response) { // loading // 如果http状态码正常,则直接返回数据 if (response) { if (response.data.status === 1) { // 成功 return response.data } else if (response.data.status === 2) { await setToken() return { status: 0, msg: '重新登录' } // 重新登录 } else if (response.data.status === 3) { // 数据格式解析异常 } else { // 异常状态下,把错误信息返回去 return { status: -404, msg: '网络异常' } } } } // 存放token到storage async function setToken() { const res = await login() AsyncStorage.setItem('token', res.token) return res.token } export const Post = async (url, params = {}) => { params = { ...params, lang: 'cn' } // 当不是登录接口时,从缓存中获取token,若不存在就调用setToken方法 if (url !== '登录接口') { const storageData = await AsyncStorage.getItem('token') params['token'] = storageData ? storageData : null if (!params.token) { params['token'] = await setToken() } } return axios .post(url, params) .then(async response => { const res = await checkStatus(response) return res.data }) .catch(function(error) { console.log(error) }) } 复制代码
- storage模块
// storage.js ,使用RN自带的AsyncStorage模块,用来储存token import {AsyncStorage} from 'react-native' // 保存数据 export const storeData = async (key, param) => { try { await AsyncStorage.setItem(key, JSON.stringify(param)) } catch (error) { // Error saving data } } // 读取数据 export const retrieveData = async key => { try { const value = await AsyncStorage.getItem(key) if (value !== null) { return value } } catch (error) { // Error retrieving data return null } } 复制代码
- 导航
导航使用React Navigation,由于需要用到抽屉导航跟普通的路由跳转,这里需要用到 stack navigator
和 Drawer navigation
结合。
- 创建导航
import React, { Component } from 'react' import { createStackNavigator, createDrawerNavigator } from 'react-navigation' import BookCar from 'views/BookCar' import UserInfo from 'views/UserInfo' import SelectPosition from 'views/SelectPosition' import ReturnPosition from 'views/ReturnPosition' import PositionDetail from 'views/PositionDetail' import BookingCarPage from 'views/BookingCarPage' // 侧面栏 import Journey from 'views/Journey/index' import ChargingRule from 'views/ChargingRule/index' import Recharge from 'views/Recharge/index' import Wallet from 'views/Wallet/index' // 抽屉内容的组件 import DrawerScreen from 'components/Ui/CustomDrawer/index' // 所有页面 const AllPage = createStackNavigator( //设置导航要展示的页面 { BookCar: { screen: BookCar }, UserInfo: { screen: UserInfo }, SelectPosition: { screen: SelectPosition }, ReturnPosition: { screen: ReturnPosition }, PositionDetail: { screen: PositionDetail }, BookingCarPage: { screen: BookingCarPage }, Journey: { screen: Journey }, ChargingRule: { screen: ChargingRule }, Recharge: { screen: Recharge }, Wallet: { screen: Wallet } }, //设置navigationOptions属性对象 { mode: 'card', //设置mode属性, headerMode: 'none' // 去掉头部 } ) // 结合抽屉跟所有页面 const DrawerNavigator = createDrawerNavigator( { Home: { screen: AllPage // 所有页面 } }, { contentComponent: DrawerScreen, // 用来呈现抽屉内容的组件 drawerWidth: 250 } ) export default class Navigator extends Component { constructor(props) { super(props) } render() { return <DrawerNavigator /> } } 复制代码
- 应用导航
// App.tsx import * as React from 'react' import Navigator from './src/components/Function/Navigator' export default class App extends React.Component { render() { return ( <Navigator /> ) } } 复制代码
4. typescript
之前做的项目用到typescript,感觉很不错,所以接入typescript。
-
install npm install react-native-typescript-transformer typescript -D
-
配置tsconfig.json
{ "compilerOptions": { "target": "es6", "module": "esnext", "moduleResolution": "node", "allowSyntheticDefaultImports": true, "rootDirs": ["./src"], "baseUrl": "./src", "jsx": "preserve", "alwaysStrict": true, "noUnusedLocals": true, "importHelpers": true, "experimentalDecorators": true, "lib": ["es7", "dom"], "skipLibCheck": true, "typeRoots": ["node", "node_modules/@types"], "outDir": "./lib" }, "exclude": ["node_modules"], "include": ["src/**/*"] } 复制代码
- 配置 the react native packager 在项目根目录创建文件
rn-cli.config.js
module.exports = { getTransformModulePath() { return require.resolve('react-native-typescript-transformer'); }, getSourceExts() { return ['ts', 'tsx']; } } 复制代码
- 加上react react-native react-navigation 的声明文件
npm install @types/react @types/react-native @types/react-navigation -D 复制代码
- 为了可以使用绝对路径,可以在需要引用的目录下创建package.json文件
例如:在api目录下创建package.json文件
{ "name": "api" } 复制代码
就可以使用 api/xxx
作为路径
- 运行验证
tsc 复制代码
5.redux
- install
npm install react-redux redux redux-actions redux-thunk -S 复制代码
- 修改App.tsx文件
import * as React from 'react' import { createStore, applyMiddleware, combineReducers } from 'redux' import { Provider } from 'react-redux' import thunk from 'redux-thunk' import Navigator from './src/components/Function/Navigator' import * as reducers from './src/redux/reducers' const createStoreWithMiddleware = applyMiddleware(thunk)(createStore) const reducer = combineReducers(reducers) const store = createStoreWithMiddleware(reducer) export default class App extends React.Component { render() { return ( <Provider store={store}> <Navigator /> </Provider> ) } } 复制代码
- actions
下面将车辆列表储存在store为例, 在 redux
目录下创建 actions
文件夹, actions
下包含 actionTypes.ts
和 CarAction.ts
actionTypes.ts
// 声明一下action类型 export const SET_CARLIST = 'SET_CARLIST' 复制代码
CarAction.ts
import * as types from './actionTypes' // action类型 // action方法 export function setCarList(carList) { return { type: types.SET_CARLIST, carList } } 复制代码
- reducers
在 redux
目录下创建 reducers
文件夹, reducers
下包含 index.ts
和 CarReducer.ts
index.ts
import CarReducer from './CarReducer' export { CarReducer } 复制代码
CarReducer.ts
import * as types from '../actions/actionTypes' const initialState = { carList: [], } export default function counter(state = initialState, action) { switch (action.type) { case types.SET_CARLIST: return { ...state, carList: action.carList } default: return state } } 复制代码
- 使用,现在已经创建好基本操作,下面就是获取store跟操作action
import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import * as CarAction from '../../redux/actions/CarAction' import * as UserInfo from '../../redux/actions/UserInfo' ... // 由于使用的ts,我们可以先定义好interface interface IStoreProps { // 方法 actions?: { setCarList: (v: CarStore.ICarItem[]) => void } // 数据 state?: { carList: CarStore.ICarItem[] } } class BookCar extends React.Component<IStoreProps> { ... // 可以通过 this.props.actions.setCarList()来执行action方法 // 同样,可以通过 this.props.state.carList来获取数据 } const StateToPoprs = state => ({ state: { ...state.CarReducer } }) const dispatchToProps = dispatch => ({ actions: bindActionCreators({ ...CarAction }, dispatch) }) export default connect( StateToPoprs, dispatchToProps )(BookCar) 复制代码
6. 使用iconfont
- install
npm install react-native-vector-icons --save 复制代码
- 配置
android/app/build.gradle
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle" project.ext.vectoricons = [ iconFontNames: [ 'iconfont.ttf'] // Name of the font files you want to copy ] 复制代码
- 获取.ttf文件
可以从iconfont上面获取,可以在上面新建一个项目,然后将需要的图标放到项目里面,点击下载至本地。
- 文件处理
下载完毕后,将 iconfont.ttf
和 iconfont.css
文件放在 src/assets/fonts/
目录下,同时将 iconfont.ttf
文件放在 android/app/src/main/assets/fonts/目录下
// iconfont.css ... .icon-jifei:before { content: "\e602"; } .icon-quan:before { content: "\e603"; } ... 复制代码
我们需要得到一个json文件,内容如下
{ "icon-jifei": 58882, "icon-quan": 58883, ... } 复制代码
其实就是将 iconfont.css
里面的样式名跟conten的值的十进制提取出来,手动转换比较麻烦,我们增加一个自动转换的脚本。 在工程根目录下新建 tools/getIconfontJson/getIconfontJson.js
const path = require('path') const oldPath = path.join('./src/assets/fonts/iconfont.css') const newPath = path.join('./src/assets/fonts/iconfont.json') var gen = (module.exports = function() { const readline = require('readline') const fs = require('fs') const fRead = fs.createReadStream(oldPath) const fWrite = fs.createWriteStream(newPath, { flags: 'w+', defaultEncoding: 'utf8' }) const objReadLine = readline.createInterface({ input: fRead }) var ret = {} objReadLine.on('line', line => { line = line && line.trim() if (!line.includes(':before') || !line.includes('content')) return var keyMatch = line.match(/\.(.*?):/) var valueMatch = line.match(/content:.*?\\(.*?);/) var key = keyMatch && keyMatch[1] var value = valueMatch && valueMatch[1] value = parseInt(value, 16) key && value && (ret[key] = value) }) objReadLine.on('close', () => { console.log('readline close') fWrite.write(JSON.stringify(ret), 'utf8') }) }) gen() 复制代码
运行脚本之后会在 assets/fonts/
目录下生成iconfont.json文件。
最后,我们在 components
目录下创建一个公共组件 IconFont
// IconFont/index.tsx import { createIconSet } from 'react-native-vector-icons' const glyphMap = require('assets/fonts/iconfont.json') const IconFont = createIconSet(glyphMap, 'iconfont', 'iconfont.ttf') export default IconFont 复制代码
- usage
import Icon from 'components/Ui/IconFont/index' ... export default class CarList extends React.Component { ... render() { <Icon name="icon-jifei" size={50} color="red" /> } } 复制代码
7.BackHandler
- 监听设备上后退事件,当我选择了车辆点的时候弹出底部框,此时,点击后退按钮的时候应该是低部框消失。
- 当切换到别的页面的时候,应该销毁之前的监听事件。
... handleBackPress = () => {} // 监听事件 backHandlerListen = () => { BackHandler.addEventListener('hardwareBackPress', this.handleBackPress) } // 销毁监听 destroyBackHandlerListen = () => { BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress) } // 重新进入这个页面触发 didFocus = () => { this.props.navigation.addListener('didFocus', payload => { this.backHandlerListen() }) } // 这个页面失去焦点触发 didBlur = () => { this.props.navigation.addListener('didBlur', payload => { this.destroyBackHandlerListen() }) } componentDidMount() { this.backHandlerListen() this.didFocus() this.didBlur() } ... 复制代码
8. 页面效果
页面暂时比较粗糙,demo性质的效果,实现主要租车功能。
9. 安卓打包APK文档
- 使用
keytool
生成秘钥
keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000 复制代码
- 在工程目录下
android/gradle.properties
加上以下配置
// *****是刚才生成秘钥时候填写的密码 MYAPP_RELEASE_STORE_FILE=my-release-key.keystore MYAPP_RELEASE_KEY_ALIAS=my-key-alias MYAPP_RELEASE_STORE_PASSWORD=***** MYAPP_RELEASE_KEY_PASSWORD=***** 复制代码
- 配置bulid.gradle
... android { ... defaultConfig { ... } signingConfigs { release { if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) { storeFile file(MYAPP_RELEASE_STORE_FILE) storePassword MYAPP_RELEASE_STORE_PASSWORD keyAlias MYAPP_RELEASE_KEY_ALIAS keyPassword MYAPP_RELEASE_KEY_PASSWORD } } } buildTypes { release { ... signingConfig signingConfigs.release } } } ... 复制代码
- 生成APK包
$ cd android $ ./gradlew assembleRelease 复制代码
生成的apk文件位于 生成的 APK 文件位于android/app/build/outputs/apk/app-release.apk
, 可以直接将apk放到手机安装 5. 遇到报错
// error Couldn't follow symbolic link' when testing release build for Android // fix rm -rf node_modules && npm install 复制代码
以上所述就是小编给大家介绍的《ReactNative仿某租车软件》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 某租车系统Java代码审计之后台注入漏洞
- 上海首批纯电动出租车9月亮相 一键报警等功能成亮点
- “软件吃掉软件”:程序员未来会消失吗?
- 百度软件中心版 PuTTY 被曝恶意捆绑软件
- 软件复用导致的软件依赖问题 - research!rsc
- 『互联网架构』软件架构-软件系统设计(一)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。