React Native已经推出好几年,越来越多的app在使用该技术进行项目的开发,而今年3月份安卓阵营的快应用的推出,让大前端的理念可能随之被提出,随着React Native的越来越成熟,以及快应用的出现,我们是选择还是放弃呢?这就要根据公司的选择了,但不管怎么我们还是有必要了解一下。
React Native (简称RN)是Facebook于2015年4月开源的跨平台移动应用开发框架,是Facebook早先开源的UI框架 React 在原生移动应用平台的衍生产物,目前支持iOS和安卓两大平台。RN使用Javascript语言,类似于HTML的JSX,以及CSS来开发移动应用,因此熟悉Web前端开发的技术人员只需很少的学习就可以进入移动应用开发领域。React Native使你能够在Javascript和React的基础上获得完全一致的开发体验,构建世界一流的原生APP。
来来来,开始我们的RN之旅吧!
一、环境安装
这个不多说了,根据RN官方文档来吧,链接地址如下https://reactnative.cn/docs/0.44/getting-started.html
注意:
这里有些需要注意下,按照上面的步骤执行下来,安装的是RN最新的版本,目前是5.1 init命令默认会创建最新的版本,而目前最新的0.45及以上版本需要下载boost等几个第三方库编译。这些库在国内即便翻墙也很难下载成功,导致很多人无法运行iOS项目
!!!中文网在论坛中提供了这些库的国内下载链接。如果你嫌麻烦,又没有对新版本的需求,那么可以暂时创建'0.44.3'的版本。
react-native init JJ1 --version 0.44.3
cd JJ1
react-native run-ios
到这里,我相信你RN的环境应该搞定,开始你的第一个项目了!
二、项目开发
1.准备工作:
在开始新项目之前,我们首先要了解 ---- 组件的生命周期(相当于OC中的控制器的声明周期)
先来一张图吧!
var List = React.createClass({
//1.创建阶段
getDefaultProps:function(){
//在创建类的时候被调用 默认props设置
console.log('getDefaultProps');
},
//2.实例化阶段
getInitialState:function(){
//获取this.state的默认值
console.log('getInitialState');
return {};
},
componentWillMount:function(){
//在render之前调用该方法
//业务逻辑的处理应该放在这里,如对state的操作等
console.log('componentWillMount');
},
render:function(){
//渲染并返回一个虚拟的DOM
console.log('render');
return ( <View > <Text >厉害了,我的国</Text></View>
);
},
componentDidMount:function(){
//该方法发生在render方法之后,在该方法中,ReactJS会使用render方法返回的虚拟DOM对象来创建真实的DOM结构
console.log('componentDidMount');
},
//更新阶段
componentWillReceiveProps:function(){
//该方法发生在this.props被修改或者父组件调用了setProps()方法之后
console.log('componentWillReceiveProps');
},
shouldComponentUpdate:function(){
//是否需要更新
console.log('shouldComponentUpdate');
return true;
},
componentWillUpdate:function(){
//将要更新
console.log('componentWillUpdate');
},
componentDidUpdate:function(){
//更新完毕
console.log('componentDidUpdate');
},
//4.销毁阶段
componentWillUnmount:function(){
//销毁时被调用,定时器要在这里注销
console.log('componentWillUnmount');
}
});
对组件的生命周期有一定了解,我们就开始我们的项目啦!
2.项目实践
项目一般是是由一个新特性页面或者引导页开始的,那我们第一件事就是开始我们的引导页创建
通过ScrollView组件实现轮播的效果,实现代码如下
import React, { Component } from 'react';
// var Header = require('./Header');
import {
AppRegistry,
Dimensions,
PixelRatio,
View,
ScrollView,
Text,
NavigatorIOS,
Image,
StyleSheet,
TouchableOpacity
} from 'react-native';
import Util from './Views/Common/util';
import App from './app';
module.exports=React.createClass({
render() {
return (
<NavigatorIOS style={{flex:1}}
initialRoute={{
title:'',
component:LaunchView,
navigationBarHidden:true,
}}
renderScene ={this._renderScene} ></NavigatorIOS>
);
},
_renderScene(route,navigator){
return (
<route.component navigator={navigator} {...route} />
)
}
});
var LaunchView = React.createClass({
getInitialState:function(){
return {
data:['iPhone se_b1','iPhone se_b2','iPhone se_b3','iPhone se_b4'],
currentPage:0
};
},
render:function(){
return (<View style={{flex:1}}><ScrollView
ref='scrollView'
horizontal={true}
//当值为true时显示滚动条
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
//当值为true时,滚动条会停在滚动视图的尺寸的整数倍位置。这个可以用在水平分页上
pagingEnabled={true}
alwaysBounceHorizontal ={false}
alwaysBounceVertical={false}
bounces ={false}
automaticallyAdjustContentInsets={false}
// contentContainerStyle={styles.scroll}
onMomentumScrollEnd = {(e)=>this._onAnimationEnd(e)}
>
{this._launchScrollWithData()}
</ScrollView>
<View style={styles.pageViewStyle}>
{this._renderAllIndicator()}
</View>
</View>);
},
_launchScrollWithData(){
var imgs =[];
let data=this.state.data;
var show =false;
for (let i = 0; i < data.length; i++) {
var url = data[i];
if (i === data.length-1 ) {
show = true;
}
imgs.push(<LaunchItem key={i} num={i} url={url} show={show} {...this.props}/>);
}
return imgs;
},
/**2.手动滑动分页实现 */
_onAnimationEnd:function(e) {
//求出偏移量
let offsetX = e.nativeEvent.contentOffset.x;
//求出当前页数
let pageIndex = Math.floor(offsetX / Util.size.width);
//更改状态机
this.setState({ currentPage: pageIndex });
},
/**3.页面指针实现 */
_renderAllIndicator() {
let indicatorArr = [];
let style;
let imgsArr = this.state.data;
for (let i = 0; i < imgsArr.length; i++) {
//判断
style = (i==this.state.currentPage)?{color:'#666'}:{color:'#ddd'};
indicatorArr.push(
<Text key={i} style={[{fontSize:30},style]}>
•
</Text>
);
}
return indicatorArr;
}
});
var LaunchItem=React.createClass({
render:function(){
var num = this.props.num;
var url = this.props.url;
var show = this.props.show;
return (<View style={[styles.img]}>
<View style={styles.img}>
<Image style={styles.img} source={{uri:url}}>
{show ? <TouchableOpacity style={styles.btn} onPress={this.btnDidClick}><View ><Image style={styles.btnContent} source ={{uri:'btn_op'}}></Image></View></TouchableOpacity>
:<View/>}
</Image>
</View>
</View>);
},
btnDidClick:function(){
// console.log('我被点击了');
this.props.navigator.replace({
component:App
});
}
});
var styles = StyleSheet.create({
img:{
width:Util.size.width,
height:Util.size.height
},
btn:{
width:120,
height:40,
marginLeft:(Util.size.width -120)/2,
marginTop: Util.size.height -150,
// alignSelf: 'center',
// justifyContent:'center',
// alignItems:'center'
},
btnContent:{
backgroundColor:'#3FA9C4',
borderRadius:10,
width:120,
height:40,
},
title:{
fontSize:16,
color:'#fff',
justifyContent:'center',
alignItems:'center',
},
pageViewStyle:{
height:25,
width:Util.size.width,
backgroundColor:'rgba(0,0,0,0)',
position:'absolute',
bottom:20,
flexDirection:'row',
alignItems:'center',
justifyContent:'center'
}
});
一般引导页的显示只是第一次打开才会显示,程序再次打开的时候我们就直接到首页,或者其他的指定页面,这里我们需要通过AsyncStorage做一些简单的存储。AsyncStorage是一个简单的,具有异步特性的键值对的存储系统,相对这个app而言,它是全局的。
因为AsyncStorage是异步的,所以在使用AsyncStorage的时候,会出现一段时间的白屏现在,我们这里先显示一张图片做类似启动页的效果,在程序读取到数据的时候,替换它的路由特性就好。
实现代码如下:
export default class JJ1 extends Component {
render() {
return (
<NavigatorIOS style={{flex:1}}
initialRoute={{
title:'',
component:LaunchImg,
navigationBarHidden:true,
}}
renderScene ={this._renderScene} ></NavigatorIOS>
);
}
_renderScene(route,navigator){
return (
<route.component navigator={navigator} {...route} />
)
}
}
var LaunchImg = React.createClass({
render() {
return (
<View style = {{flex:1}} >
<Image source={{uri:'start_se'}} style={{width:Util.size.width,height:Util.size.height}}></Image>
</View>
);
},
componentDidMount:function(){
this.timer = setTimeout(this.openApp(),2000);
},
openApp(){
AsyncStorage.getItem('isFirstLaunch',(error,result)=>{
if (result === 'false') {
console.log('不是第一次打开');
this.props.navigator.replace({
component:App
});
}else{
console.log('第一次打开');
AsyncStorage.setItem('isFirstLaunch','false',(error)=>{
if (error) {
alert(error);
}
});
this.props.navigator.replace({
component:Intro
});
}
});
},
componentWillUnmount() {
// 如果存在this.timer,则使用clearTimeout清空。
// 如果你使用多个timer,那么用多个变量,或者用个数组来保存引用,然后逐个clear
this.timer && clearTimeout(this.timer);
}
});
到这里引导页就已经完成,下面我们就创建TabBar,这里可以自定义TabBar从而实现iOS 和安卓能够同时使用,这里偷懒了,直接使用TabBarIOS去实现了!
实现代码如下:
module.exports = React.createClass({
getInitialState:function(){
return {
selectedTab:'图书'
};
},
render:function(){
return(<TabBarIOS>
<TabBarIOS.Item
title = "图书"
selected={this.state.selectedTab === '图书'}
icon = {require('./images/book.png')}
onPress ={()=>{
this.setState({
selectedTab:'图书'
});
}}>
<Book/>
</TabBarIOS.Item>
<TabBarIOS.Item
title="电影"
selected = {this.state.selectedTab ==='电影'}
icon = {require('./images/movie.png')}
onPress = {()=>{
this.setState({
selectedTab:'电影'
});
}}>
<Movie />
</TabBarIOS.Item>
<TabBarIOS.Item
title="音乐"
selected = {this.state.selectedTab === '音乐'}
icon = {require('./images/music.png')}
onPress = {()=>{
this.setState({
selectedTab:'音乐'
});
}}>
<Music/>
</TabBarIOS.Item>
</TabBarIOS>);
}
});
写到这里,基本的页面搭建就有了!
剩下就是对一些组件的学习,ListView实现iOS中TableView 的功能
例如我在图书页面通过ListView实现了列表展示,
以及在电影页面通过ListView实现九宫格的功能,这里类似iOS 中CollectionVIew
以及写了个简单的登录界面
这里页面还没有完全写完,还在持续撸代码中...
下面贴出来图书页的代码:
module.exports = React.createClass({
render:function(){
return(<NavigatorIOS
style={{flex:1}}
initialRoute={{
component:BookListView,
title:'图书',
passProps:{},
}}
renderScene ={this._renderScene} ></NavigatorIOS>
);
},
goToLogin:function(){
this.props.navigator.push({
component:Login,
title:'登录',
});
},
_renderScene(route,navigator){
return (
<route.component navigator={navigator} {...route} />
)
}
});
var BookListView = React.createClass({
getInitialState:function(){
var ds = new ListView.DataSource({rowHasChanged:(r1,r2)=>r1!==r2});
return{
dataSource:ds.cloneWithRows([]),
keywords:'c语言',
show:false
};
},
render:function(){
return(
<ScrollView style={[styles.flex,styles.containerTop]}>
<View><Search></Search></View>
{this.state.show?
<ListView dataSource={this.state.dataSource}
renderRow={this._renderRow}
renderHeader={this._headerView}
/>:Util.loading}
</ScrollView>);
},
componentDidMount:function(){
this.getData();
},
//渲染图书列表
_renderRow:function(row,sectionID,rowId){
return(<BookItem row={row} onPress={this.goToNext.bind(this,row.id,rowId)}></BookItem>);
},
_changeText:function(val){
this.setState({
keywords:val
});
},
_search:function(){
this.getData();
},
//根据关键字查询
getData:function(){
var ds = new ListView.DataSource({rowHasChanged:(r1,r2)=>r1!==r2});
var that = this;
var baseUrl = ServiceURL.book_search+'?count=10&q='+this.state.keywords;
//开启loading
this.setState({
show:false
});
//get获取数据
Util.get(baseUrl,function(data){
if (!data.books || !data.books.length) {
return alert('图书服务出错');
}
var books = data.books;
that.setState({
dataSource:ds.cloneWithRows(books),
show:true
});
},function(err){
alert(err);
});
},
goToNext:function(id,rowId){
console.log(rowId);
if (rowId === '0') {
this.props.navigator.push({
component:Login,
title:'登录',
});
}else{
this.props.navigator.push({
component:BookDetail,
title:id,
passProps:{id:id},
});
}
},
_headerView:function(){
return (<View style={{height:40},{alignItems:'center'},
{flexDirection:'row'}}>
<Text style={{marginLeft:15}}>图书表头</Text></View>);
}
});
下面就不在贴电影和登录页的代码了,直接给码云的git的地址:https://gitee.com/lumic/SouApp.git
代码比较乱,里面很多是一些实践的东西,只是作为自己学习的记录。