React Native初探

大部分简单的功能实现官方文档的说明都很清楚,我这里主要说一下我在开发过程中遇到的一些棘手的问题和解决办法

1.布局和视觉样式部分

a.RN中如何实现FrameLayout效果

如果要在RN中实现类似Android中的FrameLayout效果,需要对view的style设置 positon: ‘absolute’ ,以及一些方向属性

goods_left_label_style: {
    position: 'absolute',//相对父元素进行绝对定位
    left: 0,
    top: 0,
    right:0,
    bottom:0 //后面四个根据需要设置
},

b.等比例权重布局

要进行等比例权重布局需要使用一个重要的属性,flexDirection,该属性用于指定主轴的方向。即指定子view的布局方向。可以设置两个值分别是:row和column。感觉上类似于orientation。接着在子布局中设置flex属性就是view的权重了,类似于android中的weight。

除了flexDirection之外还有几个很重要的属性,下面说明一下他们的使用方法:

justifyContentalignItems,这两个属性是相对的,第一个是设置子View的水平方向布局,第二个是设置VIew的竖直方向布局,他们的取值有相同也有不同。这两个属性都可以取三个值,分别是flex-start,flex-end,center,意思分别是顶部对齐,底部对齐,居中对齐。

接着说他们不同的部分,justifyContent还有一个stretch属性,意思是在竖直方向上填充整个父布局;alignItems有两个分别是space-between和spage-around属性,这两个很特别在android上是没有的,space-between意思是第一个子组件位于父容器左端,最后一个子组件位于父容器最右端。然后平均分配在父容器水平方向上。space-around指所有子组件平均分配在父容器的水平方向上,左右都有留空隙。

以上的两个属性都是父布局管理子布局的属性,还有一个属性是alignSelf,他是用来单独设置组件竖直方向的样式,与alignItem类似,只是一个设置父一个设置子。

flexWrap用于设置子view是否可以换行,有两个属性可设置nowrap和wrap。nowrap:即使空间不够也不换行。wrap:空间不够的话自动换行。

c.View中设置Style

多个style可以组合起来使用。比如:

<Text style={[styles.bigblue, styles.red]}>red, then bigblue</Text>
const styles = StyleSheet.create({
  bigblue: {
    color: 'blue',
    fontWeight: 'bold',
    fontSize: 30,
  },
  red: {
    color: 'red',
  },
});

因为后写的red所以,里面的color会覆盖掉之前bigblue的color,但是其他的属性仍然沿用bigblue中的。

d.点击事件

在RN中的点击事件似乎必须在需要点击的View外部嵌套一个”Touchable”开头的一系列组件,这些组件通过onPress属性接受一个点击事件的处理函数,在函数中处理相应的点击事件。看一下代码:

<TouchableOpacity onPress = {this._onPressBack}>
    <Image style = {styles.title_btn_style} source = {THUMB_URLS[0]} resizeMode = "contain"/>
</TouchableOpacity>

_onPressBack(){
    AppModule.finishActivity()
}

e.显示或隐藏view

RN感觉并没有像Android那样提供visible这样的属性,可以直接通过设置visible来控制view的显示。如果要控制view的显示的话,需要在render view的时候设置相应的显示隐藏方法。再通过state状态值的改变来在交互过程中进行更新。看下代码:

render() {
    return (
        <View style = {{flex:1}}>
            <View style = {styles.occupy} />
            {this._getPersonalActivityRemindView()}
            {this._getPullToRefreshListView()}
        </View>
    );
}

_getPersonalActivityRemindView() {
    if (this.state.personalActivityRemind !== '') {
        return (
            //自定义view
        )
    }
}

2.针对新手关于ES5和ES6的问题

网上关于RN的文章越来越多了,但是随着ES6的普及,网上大部分的代码都是有的用ES5有的用ES6,还有两个混着用的。我在一开始不是很熟悉看得时候很容易错乱,推荐在看官方文档的时候可以顺便看一眼这篇文章,我涉及到的大部分ES的问题这里都说到了。
react-native-的es5-es6写法对照表,而且从0.18开始,RN的新项目默认模板已经全面转向ES6了。

3.使用ref可以在外部使用view的引用

使用ref可以在某些view不方便通过props或state更新的时候使用,主要是在render外面使用view的引用。

render: function() {
    return <TextInput ref={(c) => this._input = c} />;
  },
  componentDidMount: function() {
    this._input.focus();
  },

上面的代码通过制定引用,在外部的componentDidMount方法中便可食用此view的引用。ref同样可以使用字符串,例如:ref = “listview”,这种方式在外部使用的时候需要这样调用:this.refs.listview。剩余细节的使用方法可以参考文章:Refs to Components

4.ListView使用遇到的一些问题

a.RN在0.24.1版本之后ListView在使用过程中会出现empty section headers 的警告。

Warning: In next release empty section headers will be rendered. In this release you can use ‘enableEmptySections’ flag to render empty section headers.

如果需要去除这个警告的话需要在listview中添加enableEmptySections = {true}

b.RN官方的ListView中本身提供了上拉分页刷新,在ListView组件中使用onEndReach()方法可以很简单的实现上拉分页加载效果。同时,针对下拉刷新,RN官方的ListView也提供了RefreshControl来管理,可以在ListView中直接使用RefreshControl来实现下拉刷新效果。

<ListView
    refreshControl={
      <RefreshControl
        refreshing={this.state.refreshing}
        onRefresh={this._onRefresh.bind(this)}//刷新方法需要自己实现
        onEndReached = {this._onEndReached}//滚动到底是加载数据的方法需自己实现
        onEndReachedThreshold = {40}//这个参数表示距离底部多少时出发onEndReached
      />
    }
    ...

5.自定义view

先总结一下在原有组件之上定义自己的view。目前还不会完全自定义view的实现。

1.自定义自己需要的属性,类似于Android中的attrs,首先需要声明属于自己的PropTypes以及他们的类型,例如:

propTypes: {
    customStyles: React.PropTypes.object,
    initialListSize: React.PropTypes.number,
    firstLoader: React.PropTypes.bool,
    paginationFetchingView: React.PropTypes.func,
    onFetch: React.PropTypes.func,
}

这样声明相当于设置一个验证器,当向 props 传入无效数据时,JavaScript 控制台会抛出警告。

2.通过getDefaultProps方法可以给自定义的属性设置默认值

getDefaultProps() {
return {
  customStyles: {},
  initialListSize: 10,
  firstLoader: true,
  pagination: true,
      onFetch(page, callback, options) { callback([]); },
}

3.当某个属性没有被重新定义的时候,作为自定义的view,最好可以定义一个通用的方法,即默认流程,来防止出现一些问题

renderSeparator() {
    if (this.props.renderSeparator) {
          return this.props.renderSeparator();
    }
    return (
      <View style={[this.defaultStyles.separator, this.props.customStyles.separator]} />
    );
},

4.通过getInitialState方法设置初始换的状态值,通过状态值的改变可以更新view的显示效果,例如更新listview的数据,让listview加载更多等。

getInitialState() {
  this._setPage(1);
  this._setRows([]);
  return {
    dataSource: ds.cloneWithRows(this._getRows()),
    isRefreshing: false,
    paginationStatus: 'firstLoad',
  };
},

5.setNativeProps方法

如果你通过React.createClass方法自定义了一个组件,直接给它设置样式prop是不会生效的,你得把样式props层层向下传递给子组件,直到子组件是一个能够直接定义样式的原生组件。同理,我们也需要把setNativeProps传递给由原生组件封装的子组件。

setNativeProps(props) {
  this.refs.listview.setNativeProps(props);
},

render() {
  return (
    <ListView
      ref="listview"
      dataSource={this.state.dataSource}
      renderRow={this.props.rowView}
      {...this.props}
      style={this.props.style}
    />
  );
},

其中View中设置的{…this.props}这句话也很重要,我的理解是所继承的原生view属性太多,通过这句话复用父类的方法。比如你不想自己处理点击拖动效果,就直接继承过来