React-Native初体验

最近突然对react-native感兴趣. 主要被它在iOS和安卓平台通用性吸引, 就花了几天的课余时间学习了一下. 也稍微有点小收获, 想跟大家分享一下. 这门技术在2015年也是火的可以, facebook推出的, 声称是 learn once, write anywhere(感觉就跟java一样, 一次书写,到处运行).hhhh, 我也比较捣鼓新技术, 加上之前开发过iOS, 有点移动端开发基础,后台也熟悉, 所以感觉学起来还算是轻松.

. 具体的环境配置就就不跟大家讲, 网上的教程还挺多, 也很详细.

如.
react-native init DemoRN
npm install --save react-navigation
react-native run-ios
等等.

. 跟大家分享是以下一些知识点:(涉及到内容不多, 后续继续补充哈)

  1. es6中export和import的介绍.
  2. 以及一些基础的(props, state的区别)
  3. es6中fetch进行网络请求的简单介绍.
  4. react-navigation, 这里主要介绍StackNavigator和TabNavigator,类似iOS中UINavigationController和TabBarController.
  5. 正向传值和逆向回调传值
  6. ListView的使用(类似iOS中UITableView,不过还是有点区别)
  7. 如何书写一些公共模块(将一些常用的功能抽取到一个Util类中)
  8. 以及一些全局的常量如何归类.
  9. 关于style样式.
  10. 生命周期函数

1. es6中的import和export的简单介绍.

Home.js文件
这里是导出默认的模块. Home,
注意一个js文件中只能有一个默认的导出的模块.
export default class Home extends Component {

}

index.ios.js文件
注意, 这样在使用import的时候,是可以自定义导入的默认模块的名字的
import Home from './Home';


可能你在一些文件中会看到这样的导入方式:
import React, {Component} from 'react';
这里我解释下哈
React模块是react.js文件默认导出的文件.
{Component}注意这里加了大括号, 表示react.js文件中非默认导出
(即没有default关键修饰导出的模块), 还有注意一点的是
    非默认模块在import的时候是不可以重命名的.

2. 关于props和state的简单介绍.

RN的组件其实就是一个状态机, 主要的两个参数就是props和state,
最后返回一个虚拟DOM, 进行渲染.(这也是它的高效原因之一).

props 一般用于父组件向子组件通信,在组件之间进行传值使用.
state 一般用于组件内部的状态维护, 跟新组件内部数据, 状态, 重新进行虚拟DOM的渲染.
注意:
1. 不管是props还是state的改变, 都会引发render函数的重新调用.
2. 都可以由组件自身提供的函数进行初始化.
3. props是一个父组件传递给子组件的数据流 getDefaultProps
4. state只能在自身组件中setState   getInitalState
5. getDefaultProps和getInitalState在es6中推荐用constructor进行初始化.

如:
  constructor(props) {
    super(props);
    let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.state = {
        isLoaded: false,
        dataSource: ds
    }
}

3.fetch进行网络请求的简单介绍.

一个最简单的请求就是这样:
 fetch(url).then((response) => {
        return response.json();
    }).then((responseData) => {
        callBack(responseData[objectName]);
    }).catch((error) => {
        alert(error);
    })

当然这里你也可以指定请求方式, 请求头, 以及请求参数, (由于示例,未详细介绍, 具体还请读者自行去查哈).

4 StackNavigator的使用.

注意要导入
import {StackNavigator, TabNavigator} from 'react-navigation';

StackNavigator是facebook最近才推出的react-navigation框架中所带的一个模块, 超级好用, 比之前的那些navigatorIOS好用多了. gitHub上的star也猛增.

示例:
const Nav2 = StackNavigator(
{
    personCenter: {screen: PersonCenter},
    movieList:{screen:MovieList},
},
{
    navigationOptions:{
        headerStyle:{
            backgroundColor:"#eec",
        }
    }
}
);

import PersonCenter from './MyApp/PersonCenterModule/PersonCenter';
import MovieList from './MyApp/PersonCenterModule/MovieList';
这里PersonCenter和MovieList是导入的另外两个模块, 前面的名称是可以自定义的.
navigationOptions的就是全局的导航栏的设置.(会影响每一个导航栏). 当然也可以在
每个页面进行单页面自定义.
如:
export default class MovieList extends Component {

static navigationOptions = {
    tabBarVisible: false,
    title: "科技新闻"
}
}

讲导航栏肯定少不了页面跳转.
导航栏提供了一些函数和属性.这里介绍常用的.

  this.props.navigation.navigate('需要跳转到的页面', {参数传递, 回调函数的定义})
  this.props.navigation.goBack()返回上一级
  this.props.navigation.state.params 获取传递参数的值.
  或许读者会对这样的写法有疑问:
  const {params} = this.props.navigation.state.params, 我解释下哈
  这样是相当于等价先判断this.props.navigation.state中是否有params这个属性,
  如何没有程序运行时会由警告.

  如:
跳转至homeSecond页面,并且将传递titleValue值. 这里homeSecond是在导航控制器
    {screen: PersonCenter}
这里指定的.
<Button title="详情" onPress={() => {
            const {navigate} = navigation;
            navigate('homeSecond', {titleValue: "哈哈"})
        }}/>

5 正向传值和逆向回调传值

先看代码咯:
export default class HomeSecond extends Component {
static navigationOptions = {
title: “HomeSecond”,
tabBarVisible:false
}

constructor(props) {
    super(props);
    const {params} = props.navigation.state;
    this.state = {
        titleValue: params.titleValue
    }
}

render() {
    const {navigate} = this.props.navigation;
    return (
        <View style={styles.container}>
            <Text style={styles.text}>{this.state.titleValue}</Text>
            <Button title="继续点击"
                    onPress={() => navigate('homeThird',
                        {
                            titleValue: this.state.titleValue,
                            username: "React-Native",
                            callBack: (data) => {
                                this.setState({
                                    titleValue: data
                                });
                            }
                        })
                    }/>
        </View>
    );
}
}

这里首先初始化state, 有一个属性为titleValue, 这里是作为接收下级界面回调传回的值.

navigate('homeThird',{
    titleValue: this.state.titleValue,
    username: "React-Native",
    callBack: (data) => {
        this.setState({
            titleValue: data
        });
}

由于该界面是在导航控制器(StackNavigator)的管理下,
所以该界面的this.props会    自动有一个navigatioin参数,
那么就可以使用这个navigation中的navigate进行界    面跳转和参数传递了啊.
const {navigate}  = this.props.navigation;
利用该参数先进行跳转到homeThird.js页面. 并且传递titleValue和username的值,
以及一个callback的回调函数.



下面看homeThird.js页面如何接收的.

  这里接收titleValue和username.
  constructor(props) {
    super(props);
    this.state = {
        textInputValue: props.navigation.state.params.titleValue,
        username:props.navigation.state.params.username
    }
}

render() {
    const {params} = this.props.navigation.state;
    return (
        <View style={styles.container}>
            <Text style={styles.text}>{params.username}</Text>
            <TextInput style={styles.textInputViewStyle}
                       placeholder="请输入"
                       onChangeText={(text) => this.setState({textInputValue: text})}/>
            <Button title="确认并返回"
                    onPress={this.clickBackBtn.bind(this)}/>
        </View>
    );
}

    clickBackBtn() {
    const {goBack} = this.props.navigation;
    const {params} = this.props.navigation.state;
    params.callBack(this.state.textInputValue);
    goBack();
}

这里先获取到前一个界面传递过来的参数params,
得到回调函数callBack进行回调传值. 将输入框中的值逆传给上一个界面.

6. TabNavigator的简单使用

注意要导入
import {StackNavigator, TabNavigator} from 'react-navigation';

和StackNavigator非常相似.

const HelloRN = TabNavigator(
{
    first: {
        screen: Nav1,
        navigationOptions: {
            tabBarLabel: '主页',
            tabBarIcon: ({tintColor}) => (
                <Image style={{tintColor: tintColor}} source={require('./MyApp/images/tab_groups@2x.png')}/>)
        },
    },
    second: {
        screen: Nav2,
        navigationOptions: {
            tabBarLabel: "我的",
            tabBarIcon: ({tintColor}) => (
                <Image style={{tintColor: tintColor}} source={require('./MyApp/images/tab_settings@2x.png')}/>)
        }
    },
},

//这里设tabbar的一些全局属性, 会影响所有的页面中的tabbar.
{
    animationEnabled: false, // 切换页面时是否有动画效果
    tabBarPosition: 'bottom', // 显示在底端,android 默认是显示在页面顶端的
    swipeEnabled: false, // 是否可以左右滑动切换tab
    backBehavior: 'none', // 按 back 键是否跳转到第一个Tab(首页), none 为不跳转
    tabBarOptions: {
        activeTintColor: '#17e', // 文字和图片选中颜色
        inactiveTintColor: '#666', // 文字和图片未选中颜色
        showIcon: true, // android 默认不显示 icon, 需要设置为 true 才会显示
        indicatorStyle: {
            height: 0  // 如TabBar下面显示有一条线,可以设高度为0后隐藏
        },
        style: {
            backgroundColor: '#eee', // TabBar 背景色
            height: 44, //默认为44
        },
        labelStyle: {
            fontSize: 14, // 文字大小
        },
    },
}
);

#7 ListView的使用

//创建一个DataSource数据源.
constructor(props) {
    super(props);
    //这里进行DataSource的创建, 指定渲染策略.
    let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.state = {
        isLoaded: false,
        dataSource: ds
    }
}

//进行数据请求. 获取数据,更新state 进而重新渲染页面,
//注意只要state中的值发生变化就会进行render方法的调用.
componentDidMount() {
    Util.getRquest(Constants.IMOOC_API, (data) => {
        this.setState({
            dataSource: this.state.dataSource.cloneWithRows(data),
            isLoaded: true
        });
    }, "data");
}

页面渲染
render() {
    if (!this.state.isLoaded) {
        return this.renderLoadingView();
    }
    return (
        <View style={styles.container}>
            <ListView
                style={styles.listView}
                dataSource={this.state.dataSource}
                renderRow={this._renderRow.bind(this)}
                initialListSize={10}/>
        </View>
    );
}

渲染每一行
_renderRow(model) {
    return (
        <View style={styles.rowContainer}>
            <Image source={{uri: model.picSmall}} style={styles.cellImage}/>
            <View style={styles.textContainer}>
                <Text style={styles.title}>{model.name}</Text>
                <Text style={styles.subTitle}>{model.description}</Text>
            </View>
        </View>
    )
}

//加载等待的view
renderLoadingView() {
    return (
        <View style={{flex: 1, alignItems: "center", justifyContent: "center"}}>
            <Text style={{fontSize: 18, color: "#17e"}}>
                加载中...
            </Text>
        </View>
    );
}

#8 公共模块和全局常量的书写.

Constants.js文件

/**
 *定义APP需要要的全局常量文件.
 * 存放全局的const, 如:公共的API, 以及主题等.
 */

const Constatnts = {
    /**
     * 新闻接口API
     */
    NEWS_API : 'http://c.m.163.com/nc/article/list/T1348649580692/0-20.html',
    /**
     * 慕课网API
     */
    IMOOC_API:"http://www.imooc.com/api/teacher?type=4&num=30"
};

export default Constatnts;

MyUtils文件如下. 常用的工具如设备屏幕的宽高, 设备的操作系统.以及get请求方法的封装.

const Dimensions = require('Dimensions');
const Platform = require('Platform');

export default class MyUtils {

static getScreenWidth() {
    return Dimensions.get('window').width;
}

static getScreenHeight() {
    return Dimensions.get('window').height;
}

static getPlatformOS() {
    return Platform.OS;
}

static getRquest(url, callBack, objectName) {
    fetch(url).then((response) => {
        return response.json();
    }).then((responseData) => {
        callBack(responseData[objectName]);
    }).catch((error) => {
        alert(error);
    })
}
}

#9 关于style样式的简单介绍
你可以内联这样写, 注意这里要加两个大括号, 外层大括号是javascript的语法. 内层大括号是jsx的语法.

<View style={{flex:1,alignItems:"center", justifyContent:"center"}}>
   <Text style={{ fontSize:18, color:"#17e"}}>
       加载中...
   </Text>
</View>

当然, 更推荐这样的方式(外联).

const styles = StyleSheet.create({
ListView: {
    marginTop: 20,
    backgroundColor: "#F5FCFF"
},
rowContainer: {
    flexDirection: "row",
    flex: 1,
    paddingTop: 5,
    paddingBottom: 5,
    borderBottomWidth: 1,
    borderColor: "#666"
},
image: {
    width: 120,
    height: 100,
    marginLeft: 10
},
textContainer: {
    flex: 1,
    paddingLeft: 10,
    paddingRight: 10,
    justifyContent: "space-around"
},
title: {
    color: "black",
    fontSize: 15
},
subtitle: {
    color: "#666",
    fontSize: 13,
    textAlign: "right",
}
});

/**
 * justifyContent是垂直方式
 * alignItems是水平方式
 *
 * 具体的样式有:
 * 1. center 容器内居中对齐
 * 2. flex-start 从容器的头部开始排列
 * 3. flex-end 从容器的底部开始排列
 * 4. space-around 容器内各组件以及与容器之间间距相等
 * 5. space-between 容器内各组之间间距相等(不包含和容器之间)
 */

总结

好了, 今天就介绍到这里了, 一些常用的简单功能基本上都讲了哈. 可能由于我的水平有限, 讲的不是很清楚, 希望读者谅解. 待我再学些时日, 再来向大家献丑. hhhh(腹黑). 谢谢!