写在前面

上周把基于Redux的单页应用开发完 紧接着就开始了ReactNative的开发。真的快得不可思议,只花了一周时间,我们两个人就分工把APP也开发完了,并且同时兼容IOS操作系统和Android操作系统。内部测试了一轮,流畅性和用户体验方面也都相当给力! 接下去几篇依次介绍项目开发中领悟的技巧和遇到的坑。

项目架构

React开发的单页应用不同,ReactNative开发不需要依赖webpackfacebook已经提供的一套基于NodeJS的转换和运行工具,这里不多做介绍。项目的架构如下:

|---- project |---- android                          // android外壳 |---- ios                              // ios外壳|---- node_modules                     // 项目依赖的node模块|---- app                              // 本项目的js代码|---- actions                      // Redux的actions|---- assets                       // 项目使用的icon|---- components                   // 项目自定义的组件|---- containers                   // 项目的容器页面|---- mixins                       // 全局使用的工具方法|---- modules                      // 全局使用的自定义模块  |---- reducers                     // Redux的reducers|---- configureStore.js            // Redux的store配置|---- index.js                     // APP入口页面|---- index.ios.js                     // android入口|---- index.android.js                 // ios入口|---- packge.json                      // 项目包信息

Redux不清楚的童鞋可以出门看我之前的《基于Redux的单页应用开发总结》 。其实除了路由,其他和单页应用的结构差别不大。

依赖的Node模块

ReactNative开发离不开NodeJS的支持,相比单页应用,这里依赖的模块比较少,除了Redux相关的模块,其他都是项目中用到的三方组件。

"dependencies": {"@remobile/react-native-toast": "^1.0.4",          // 用于错误提示的小弹窗"base-64": "^0.1.0",                              "react": "^0.14.8","react-native": "^0.25.1","react-native-animatable": "^0.6.0",               // 动画库"react-native-communications": "^2.0.0",           // 调用手机通讯功能"react-native-image-picker": "^0.18.17",           // 图片选择"react-native-modalbox": "^1.3.3",                 // 模态框"react-native-tab-navigator": "^0.2.18",           // 导航栏"react-native-viewpager": "^0.2.1",                // 图片轮播切换"react-redux": "^4.4.5","redux": "^3.5.2","redux-thunk": "^2.0.1"}

错误提示

App里错误提示和网站稍有不同,网站页面宽度大,错误提示信息一般出现在输入框的后面或者悬浮在右上角。但是在App里,需要考虑用户不同的屏幕尺寸,错误信息应该放在一个相对独立且不影响其他元素显示的位置,目前主流的展现形式是这样的:

即悬浮放置在页面的中底部,并且定时自动关闭。这个玩意自己写一个也不难,不过为了省事和好的兼容性,就直接使用三方的@remobile/react-native-toast了。使用方法如下:

import Toast from '@remobile/react-native-toast';// ...Toast.showShortBottom('用户名或密码不正确'); 

对外的API有以下几个,顾名思义:

Toast.showShortTop = function (message) {showToast(message, "short", "top");
};Toast.showShortCenter = function (message) {showToast(message, "short", "center");
};Toast.showShortBottom = function (message) {showToast(message, "short", "bottom");
};Toast.showLongTop = function (message) {showToast(message, "long", "top");
};Toast.showLongCenter = function (message) {showToast(message, "long", "center");
};Toast.showLongBottom = function (message) {showToast(message, "long", "bottom");
};Toast.show = function (message) {showToast(message, "short", "bottom");
};Toast.hide = function () {RCTToast.hide();
};

安装和使用方法请查看 官方文档

动画库

之前花时间研究过CSS3的动画库,其实目前主流的动画类型和动画创意就那么些,感兴趣的可以 clone一下 myAnimate 这个项目。一句话,CSS3里使用的动画方案,ReactNative里也应有尽有。我这边使用的是 react-native-animatable 组件。使用方式如下:

import * as Animatable from 'react-native-animatable';class ExampleView extends Component {render() {return (<TouchableOpacity onPress={() => this.setState({fontSize: (this.state.fontSize || 10) + 5 })}><Animatable.Text transition="fontSize" style={{fontSize: this.state.fontSize || 10}}>Size me up, Scotty</Animatable.Text></TouchableOpacity>);}
}

下面是官方的Demo

调用手机通讯功能

HybridApp里实现这个功能还是挺麻烦的,需要客户端封装好接口给H5调用,但是在ReactNative里,一个组件就能搞定—— react-native-communications,安装请查看官方文档

这个组件安装很简单,支持的功能有:拨号、发短信、发Email、打开网页 等 ,下面是官方一个综合的例子:

import React, { Component }  from 'react';import {AppRegistry,StyleSheet,Text,View,TouchableOpacity
} from 'react-native';import Communications from 'react-native-communications';class RNCommunications extends Component({render() {return (<View style={styles.container}><TouchableOpacity onPress={() => Communications.phonecall('0123456789', true)}><View style={styles.holder}><Text style={styles.text}>Make phonecall</Text></View></TouchableOpacity><TouchableOpacity onPress={() => Communications.email(['emailAddress1', 'emailAddress2'],null,null,'My Subject','My body text')}><View style={styles.holder}><Text style={styles.text}>Send an email</Text></View></TouchableOpacity><TouchableOpacity onPress={() => Communications.text('0123456789')}><View style={styles.holder}><Text style={styles.text}>Send a text/iMessage</Text></View></TouchableOpacity><TouchableOpacity onPress={() => Communications.web('https://github.com/facebook/react-native')}><View style={styles.holder}><Text style={styles.text}>Open react-native repo on Github</Text></View></TouchableOpacity></View>);}
});const styles = StyleSheet.create({container: {flex: 1,alignItems: 'center',backgroundColor: 'rgb(253,253,253)',},holder: {flex: 0.25,justifyContent: 'center',},text: {fontSize: 32,},
});AppRegistry.registerComponent('RNCommunications', () => RNCommunications);

访问手机相册

调取手机相册和上传图片是个老生常谈的问题,ReactNative里可以通过react-native-image-picker 来处理,安装请查看官方文档

拎一段代码片段:

import ImagePicker from 'react-native-image-picker' const options = {title: '选择上传图片', // specify null or empty string to remove the titlecancelButtonTitle: '取消',takePhotoButtonTitle: '拍照...', // specify null or empty string to remove this buttonchooseFromLibraryButtonTitle: '从库中选择...', // specify null or empty string to remove this button//customButtons: {//    'Choose Photo from Facebook': 'fb', // [Button Text] : [String returned upon selection]//},cameraType: 'back', // 'front' or 'back'mediaType: 'photo',//videoQuality: 'high', // 'low', 'medium', or 'high'maxWidth: 200, // photos onlymaxHeight: 200, // photos onlyallowsEditing: true,noData: false,
}//... onUpload() {ImagePicker.showImagePicker(options, (response) => {if (response.didCancel) {//console.log('User cancelled image picker');}else if (response.error) {//console.log('ImagePicker Error: ', response.error);} else {let source = {uri: response.uri.replace('file://', ''), isLocal: true, isStatic: true};this.setState({ form: {...this.state.form, avatar: source} })}})}

模态框

模态框在App里使用的也比较多,比如确认模态、加载模态、输入模态等,出于良好的用户体验和兼容性考虑,我这里底层采用react-native-modalbox,然后根据不同功能进行二次加工。

ConfirmModal

很常见,不多做介绍,copy下面代码 直接可以使用

import React, { Component } from 'react';
import {Dimensions,StyleSheet,Text,TouchableOpacity,View,
} from 'react-native';
import ModalBox from 'react-native-modalbox';
const styles = StyleSheet.create({modal: {borderRadius: 10,},modalContent: {flex: 1,paddingLeft: 10,paddingRight: 10,},h2: {marginTop: 15,fontSize: 20,color: '#555',textAlign: 'center',},modalOption: {flexDirection: 'row',borderTopWidth: 1,borderTopColor: '#ddd',},modalCancel: {flex: 1,padding: 15,},modalCancelText: {fontSize: 16,textAlign: 'center',},modalConfirm: {flex: 1,padding: 15,borderLeftWidth: 1,borderLeftColor: '#ddd',},modalConfirmText: {fontSize: 16,textAlign: 'center',},message: {flex: 1,justifyContent: 'center',alignItems: 'center',},messageText: {color: '#555',fontSize: 16,},
});
export default class ConfirmModal extends Component {constructor(props) {super(props);}open() {this.refs.modal.open()}close() {this.refs.modal.close()}render() {let { width } = Dimensions.get('window');return (<ModalBoxref={"modal"}style={[styles.modal, {width: this.props.width || (width-60), height: this.props.height || 200}]}backdropOpacity={0.3}position={"center"}isOpen={false}><View style={styles.modalContent}><Text style={styles.h2}>{ this.props.title || '提示' }</Text><View style={styles.message}><Text style={styles.messageText}>{ this.props.message }</Text></View></View><View style={styles.modalOption}><TouchableOpacity style={styles.modalCancel} onPress={()=> this.refs.modal.close() }><Text style={styles.modalCancelText}>取消</Text></TouchableOpacity><TouchableOpacity style={styles.modalConfirm} onPress={()=> this.props.onConfirm() }><Text style={styles.modalConfirmText}>确定</Text></TouchableOpacity></View></ModalBox>)}
}

LoadingModal

这个也很常见,copy下面代码 直接可以使用

import React, { Component } from 'react';
import {StyleSheet,
} from 'react-native';
import ModalBox from 'react-native-modalbox';
const styles = StyleSheet.create({modal: {justifyContent: 'center',alignItems: 'center',backgroundColor: 'transparent'},
});export default class LoadingModal extends Component {constructor(props) {super(props);}open() {this.refs.modal.open()}close() {this.refs.modal.close()}render() {return (<ModalBoxstyle={styles.modal}ref="modal"position={"center"}backdrop={false}isOpen={this.props.isOpen || false}//backdropOpacity={.3}backdropPressToClose={false}animationDuration={10}></ModalBox>);}
}

PickerModal

这个特别讲解一下,PickerModal用于页面上的Picker的处理,显示效果如下:

import React, { Component } from 'react';
import {Dimensions,StyleSheet,Text,TouchableOpacity,Picker,View,
} from 'react-native';
import ModalBox from 'react-native-modalbox'
import dismissKeyboard from '../mixins/dismiss-keyboard'
const styles = StyleSheet.create({popup: {},popupContent: {flex: 1,paddingLeft: 10,paddingRight: 10,},h2: {marginTop: 15,fontSize: 20,color: '#555',textAlign: 'center',},popupOption: {flexDirection: 'row',borderTopWidth: 1,borderTopColor: '#ddd',},popupCancel: {flex: 1,padding: 15,},popupCancelText: {fontSize: 16,textAlign: 'center',},popupConfirm: {flex: 1,padding: 15,borderLeftWidth: 1,borderLeftColor: '#ddd',},popupConfirmText: {fontSize: 16,textAlign: 'center',},message: {flex: 1,justifyContent: 'center',alignItems: 'center',},messageText: {color: '#555',fontSize: 16,},
});export default class PickerModal extends Component {constructor(props) {super(props);}open() {dismissKeyboard()this.refs.modal.open()}close() {this.refs.modal.close()}_renderPickerItems(data) {data.map((item)=>{return [<Picker.Item label={item[0]} value={item[1]} />]})}render() {let { width } = Dimensions.get('window');return (<ModalBoxref={"modal"}style={[styles.popup, {width: this.props.width || (width), height: this.props.height || 200}]}backdropOpacity={0.3}position={"bottom"}swipeToClose={false}isOpen={false}><View style={styles.popupContent}><Picker {...this.props}>{this.props.dataSource.map((item,i)=> {if (item.length) return <Picker.Item key={i} label={item[0]} value={item[1]} />})}</Picker></View></ModalBox>)}
}

补充说明一下dismissKeyboard()这个方法,该方法用于关闭页面的keyboard(键盘),ReactNative 默认没有这种方法,需要自己编写:

import { TextInput } from 'react-native';
const { State: TextInputState } = TextInput;
export default function dismissKeyboard() {TextInputState.blurTextInput(TextInputState.currentlyFocusedField());
}

导航条

这个组件其实ReactNative提供了原生版本的,但是样式和功能上不好控制,建议自己手写一个,代码如下:

import React, { Component } from "react";
import {Image,Platform,StyleSheet,Text,TouchableOpacity,View,
} from 'react-native';const styles = StyleSheet.create({leftButton: {marginLeft: 5,},rightButton: {marginRight: 5,},button: {width: 44,height: 44,justifyContent: 'center',alignItems: 'center',},nav: {backgroundColor: '#f9f9f9',flexDirection: 'row',alignItems: 'center',},title: {flex: 1,height: 44,justifyContent: 'center',},btnText: {fontSize: 16,color: '#777',},marginForIOS: {marginTop: 20,},titleText: {fontSize: 20,textAlign: 'center',color: '#555'}
});export class RightButton extends Component {render() {return (<TouchableOpacitystyle={styles.button}onPress={this.props.onPress}>{ this.props.text ? <Text style={styles.btnText}>{this.props.text}</Text> : null }{ this.props.icon ? <Image source={this.props.icon} style={styles.rightButton} /> : null }</TouchableOpacity>);}
}export class NavigatorBar extends Component {_leftButton() {if (this.props.navigator.getCurrentRoutes().length > 1) return (<TouchableOpacitystyle={styles.button}onPress={()=> this.props.navigator.pop() }><Image source={require('../assets/icon-nav-left.png')} style={styles.leftButton} /></TouchableOpacity>)}_rightButton() {if (this.props.rightButton) return (<RightButton {...this.props.rightButton} />)}render() {return (<View style={styles.nav}><View style={[styles.button, Platform.OS=='ios' ? styles.marginForIOS : null]}>{this._leftButton()}</View><View style={[styles.title, Platform.OS=='ios' ? styles.marginForIOS : null]}><Text style={styles.titleText}>{ this.props.name }</Text></View><View style={[styles.button, Platform.OS=='ios' ? styles.marginForIOS : null]}>{this._rightButton()}</View></View>);}
}

然后在container里就可以使用了:

import { NavigatorBar } from '../components/navigator' // 没有右侧按钮
<NavigatorBar name="登录" navigator={this.props.navigator} /> // 右侧按钮为图标
<NavigatorBar name="我的" navigator={this.props.navigator} rightButton={{onPress: ()=>{this.props.navigator.push({component: Setting})}, icon: require('../../assets/icon-set.png')}} />// 右侧按钮为文字
<NavigatorBar name="我的订单" navigator={this.props.navigator} rightButton={{text: '历史 ', onPress: ()=> this.props.navigator.push({component: OrderHitory}) }} /> 

图片轮播

建议使用三方的react-native-viewpager组件,安装请查看 官方文档

下面是一个demo:

var ViewPager = require('react-native-viewpager');
<ViewPagerdataSource={this.state.dataSource}renderPage={this._renderPage}animation = {(animatedValue, toValue, gestureState) => {// Use the horizontal velocity of the swipe gesture// to affect the length of the transition so the faster you swipe// the faster the pages will transitionvar velocity = Math.abs(gestureState.vx);var baseDuration = 300;var duration = (velocity > 1) ? 1/velocity * baseDuration : baseDuration;return Animated.timing(animatedValue,{toValue: toValue,duration: duration,easing: Easing.out(Easing.exp)});}}
/>

基于Redux的ReactNative项目开发总结(一)相关推荐

  1. 基于J2EE架构的项目开发团队中的角色与职责

    [声明] 1.2内容来源:<J2EE Architects Handbook>中文翻译<J2EE系统架构师参考手册>[翻译 Mellon] 1.角色 Technical arc ...

  2. 基于 android 创建 React-Native 项目与连接夜深模拟器

    第一步:安装好android环境(sdk ,jdk) 第二步:安装node环境,npm install -g react-native-cli (可以使用淘宝的cnpm来提高下载速度) 第三步:创建项 ...

  3. 多子系统多业务模块的复杂数据处理——基于指令集物联网操作系统的项目开发实践

    李顺,指令集Java开发工程师, 主要从事物联网业务应用.物联网数据处理等开发工作; 在杭州奥体中心体育场馆数字化.智能化升级改造工程中, 是场馆数据处理模块主要开发人员. 1 项目背景与数据处理挑战 ...

  4. 基于Vue的WebApp项目开发(四)

    实现新闻咨询页面 目录结构 步骤一:创建newslist.vue文件 <template><div id="tml"><!--使用mui框架,实现新闻 ...

  5. React项目开发中的数据管理

    原文链接:https://blog.csdn.net/hl582567508/article/details/76982756 redux中文文档:http://cn.redux.js.org/ Re ...

  6. AngryTask - 基于伪 scrum 的个人项目开发产品

    关于 去年年末的时候同事分享了一下 scrum 工作模型, 以后公司按照这种方式来执行产品开发. 联想自己在阿里的两年的工作方式和大学课程讲述的项目协同敏捷开发的一些知识. 所以本文想就开发工作流模型 ...

  7. 如何用 Python 进行基于深度学习的计算机视觉项目开发?

    令人惊喜的"智能"年代 深度学习有着广阔的前景 我们正处在一个"智能"的年代,比如智能手机中的语音助手.机器翻译和人脸识别:战胜过日本将棋冠军.国际象棋冠军, ...

  8. [转]基于Starling移动项目开发准备工作

    最近自己趁业余时间做的flash小游戏已经开发得差不多了,准备再完善下ui及数值后,投放到国外flash游戏站.期间也萌生想法,想把游戏拓展到手机平台.这两天尝试了下,除去要接入ane接口的工作,小游 ...

  9. 基于Mint UI和MUI开发VUE项目一之环境搭建和首页的实现

    一:简介 Mint UI 包含丰富的 CSS 和 JS 组件,能够满足日常的移动端开发需要.通过它,可以快速构建出风格统一的页面,提升开发效率.真正意义上的按需加载组件.可以只加载声明过的组件及其样式 ...

最新文章

  1. 学习使用React和Electron一次构建自己的桌面聊天应用程序
  2. 写 5 个你知道的 HTML5 标签,说明他们的意义
  3. AMD Zen 2新架构2018年初发布 再提升15%性能
  4. 工具类--日期工具类
  5. python程序结构有哪几种_Python数据结构与算法(几种排序)小结
  6. python测试网络连通性_Python测试网络连通性示例【基于ping】
  7. 专科计算机网络期末考试,计算机网络(专科)期末练习题.doc
  8. 数据流图技术相关基础知识
  9. 深度解析SDN——利益、战略、技术、实践(实战派专家力作,业内众多专家推荐)
  10. 阿里巴巴CTO程立:践行“好科技”,我们正在做这些事
  11. Linux4755文件权限,如何在Linux中管理文件和文件夹的权限
  12. 在quill编辑器中集成秀米和135编辑器
  13. Android 最最最简单的浏览器代码
  14. 苹果手机怎么投屏不了,苹果手机怎么投屏电脑
  15. 名师出高徒!请关注领英上这十位活跃的大神
  16. [Jzoj]3457. 沙耶的玩偶
  17. 输出一个菱形星星图案
  18. 错误解决:There is no screen to be resumed matching
  19. mac火狐浏览器不能打开任何网址问题解决方案!
  20. 细数中国历史上的十大乱世枭雄(上)

热门文章

  1. Vim和Ctags使用心得---在Taglist中加入VHDL语言
  2. 普通母函数与指数母函数
  3. 字编址与字节编址的计算方法
  4. 十七·交换机工作模式及基本命令
  5. 汉字国标码、区位码和机内码三者的定义及联系
  6. 逻辑回归和多项逻辑回归
  7. python爬虫---mechanize
  8. 窗体在任务栏显示 MFC
  9. 酒店有担保、预付和现付的付款方式
  10. 新周刊:可怕的大学: 到底有多少人在校园里浪费青春