ReactNative快应用时代,是选择还是放弃?

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中的控制器的声明周期)
先来一张图吧!


20161101195539620.jpg
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]}>
         &bull;
        </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的时候,会出现一段时间的白屏现在,我们这里先显示一张图片做类似启动页的效果,在程序读取到数据的时候,替换它的路由特性就好。


屏幕快照 2018-04-09 下午4.26.41.png

实现代码如下:

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
以及写了个简单的登录界面
这里页面还没有完全写完,还在持续撸代码中...


屏幕快照 2018-04-09 下午4.28.10.png

下面贴出来图书页的代码:

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>);
  }

});

屏幕快照 2018-04-09 下午4.28.28.png

下面就不在贴电影和登录页的代码了,直接给码云的git的地址:https://gitee.com/lumic/SouApp.git
代码比较乱,里面很多是一些实践的东西,只是作为自己学习的记录。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容