React Native 开发小Tips

相信好多写React Native的都是前端出身,当然遇见问题的,也很多时候会想从前端出发,但由于React Native本身的限制,并不是支持足够多的属性和样式,所以Bo主结合自己的开发实践,并总结了一些将来开发可能会遇见的问题并给出一些小的代码参考;(PS实现不好的希望能大家提出看法,自己也会更新)。

自己将代码放到了example下,并且做成了一个App.这样可以查看具体运行效果:

截图1:

截图2:

项目地址

开始

git clone https://github.com/JackPu/react-native-tips.git

进入example 目录

react-native start

用xcode打开ios目录下的项目,运行就可以看到上面的运行界面了。

1.关于按钮

写习惯了html我们看到按钮,第一时间想到的便是Button,但是目前React Native并没有这个组件,不过没关系,我们可以使用 TouchableHighlight,TouchableOpacity来实现按钮组件,当然常用的样式可以应用在上面,形成格式各样的按钮。

<TouchableHighlight onPress={this._onPressButton}>
      <Text>This is Button</Text>
</TouchableHighlight>

如果你实在非常喜欢按钮的话,没关系,我们引入已经封装好的组件react native button

npm install react-native-button --save

安装好后,你就可以大胆的这样写了:

<Button
    style={[Css.btn,Css.btnP]}
    styleDisabled={{color: 'red'}}
    onPress={() => this._handlePress()}>
    This is a button
  </Button>

2.文字过长隐藏的问题

CSS3中大家可能都会用到text-oveflow,然而RN 的Text并没有这个属性,不过我们可以通过设置numberOfLIne 或者JS自动计算来实现:

<Text numberOfLines={1}>your long text here<Text>

3.关于百分比宽度

写样式的时候有的时候我们经常会用到百分比,然而React Native并不支持这样的单位,除了用Flex布局外,我们可以通过另外一个方式获得:Dimensions。当然由于都是JS因此我们可以取巧,用JS计算下,比如30%,

var React = require('react-native');

var {Dimensions,StyleSheet,Component} = React;
// 我们可以使用Dimensions 去获取窗口宽度
var fullWidth = Dimensions.get('window').width; 

let thirtyPercentiWidth = fullWidth * 0.3;

// Your stylesheet
var styles = StyleSheet.create({
	.wrap{
		width: thirtyPercentiWidth,
	}
});

4.Grid列表

在App中的常用的列表除了水平列表外,我们还需要栅格化的列表。比如类似于下面这样:

做出类似的界面其实只要限制住你每一个小方块的宽度就行了。

var styles = StyleSheet.create({
  list: {
    justifyContent: 'flex-start',
    flexDirection: 'row',
    flexWrap: 'wrap'
  },
  row: {
    justifyContent: 'center',
    padding: 5,
    margin: 5,
    width: (Dimensions.get('window').width - 30) / 3,
    height: 100,
    backgroundColor: '#fff',
    alignItems: 'center',
  },
  thumb: {
    width: 55,
    height: 55
  },
  text: {
    flex: 1,
    marginTop: 10,
  }

});

// render row

 <TouchableHighlight onPress={() => this._pressRow(rowID,rowData)} underlayColor='rgba(0,0,0,0)'>
            <View>
              <View style={styles.row}>
                <Image style={styles.thumb} source={{uri: rowData['game_icon']}} />
                <Text numberOfLines={1} style={styles.text}>
                  {rowData['game_name']}
                </Text>
              </View>
            </View>
</TouchableHighlight>

详细代码

5.混合使用webview

无论什么时候,作为一个前端er,在遇到比较棘手的问题时候,我们都可以回到原点,用一个网页去解决。因此无论如何都需要学会使用React Native webview。除此之外,部分页面,其实完全可以由网页去支持多端共用的功能,楼主亲身遇到过的场景,就是图表的绘制,我们的方案是一个页面,需要微信,手机网页,和android,ios都具备该功能,而且我们手机网页和客户端打开的稍微有区别,需要隐藏header。

上图是网页版本的,而我们通过设置页面的查询参数即来自客户端的请求或者微信的都会设置为类似这样的url

https://xxx.yoursites.com/page.html?hide_header=1&client=ios

而在React Native 设置webview 的代码也很简单,你可以查看这里代码

6.设置网络请求Fetch

由于客户端也需要大量接口的支持,因此我们一定避免单兵作战,需要请求时候用个fetch,这样其实非常不易控制数据的流入。建议在fetch上在封装一次,这样我们就可以做更多的事情,比如做统一的错误提示,用户失效控制,统一设置接口请求的header,同时可以方便我们进行调试,在chrome中查看具体的接口数据等。

send(url,options) {
        var isLogin = this.isLogin();
        
        var self = this;        
        var defaultOptions = {
            method: 'GET',
            error: function() {
                options.success({'errcode':501,'errstr':'系统繁忙,请稍候尝试'});
            },
            headers:{
                'Authorization': this.getAccessToken(),
                'Accept': 'application/json',
                'Content-Type': 'application/json',
                'App': 'vanthink-ios-app'
            },
            data:{
                // prevent ajax cache if not set 
                '_regq' : self.random()
            },
            dataType:'json',
            success: function(result) {}
        };
        
        var options = Object.assign({},defaultOptions,options);
        var httpMethod = options['method'].toLocaleUpperCase();
        var full_url = '';
        if(httpMethod === 'GET') {
            full_url = this.config.api +  url + '?' + this.serialize(options.data);
        }else{
            // handle some to 'POST'
            full_url = this.config.api +  url;
        }
        
        if(this.config.debug) {
            console.log('HTTP has finished %c' + httpMethod +  ':  %chttp://' + full_url,'color:red;','color:blue;');
        }
        options.url = full_url;
        
        
        var cb = options.success;
      
        // build body data 
        if(options['method'] != 'GET') {
            options.body = JSON.stringify(options.data);
        }
  
        // todo support for https
        return fetch('http://' + options.url,options)
               .then((response) =>  response.json())
               .then((res) => {      
                    self.config.debug && console.log(res);
                    if(res.errcode == 101) {
                        return self.doLogin();
                    }

                    if(res.errcode != 0) {

                        self.handeErrcode(res);
                    }  
                    return cb(res,res.errcode==0);
                })
                .catch((error) => {
                  console.warn(error);
                });
    },
    
    
    handeErrcode: function(result) {
        // not login
        if(result.errcode == 123){
           // your code to do
            
            return false; 
        }
       
        return this.sendMessage(result.errstr);
    },

7.管理你的Icon

在网页中我们经常可以看到非常多的小的icon,我们习惯性的用Css Sprite 和 Icon Font或者 Svg去解决这些问题。移步到客户端,同样,我们也有很多解决方案,但是有一点必须要明确,将icon放到同一个地方,方便管理。这里有很多第三方库选择:

如果自己写的话,可以写到一个组件中,通过设置一个基类,然后进行继承和导出。设置不同的图标思路大概如下:

import React, { TouchableHighlight,View,Text, Image, StyleSheet, PropTypes } from 'react-native';

// 基本的样式
let styles = StyleSheet.create({
  icon: {
    width: 21,
    height: 21,
    marginTop: 4,
    marginRight: 15,
  }, 
});

class Icons extends React.Component { 
    constructor(props) {
        super(props);
        this.press = this.press.bind(this);
      }

      press() {
        if(typeof this.props.press == 'function') {
            this.props.press();
        }else{
            // TODO
        }
        
      }
      _renderIcon() {
        return (
            <Image source={require('../images/baseicon.png')} style={styles.icon} />
        );  
      }

      render() {
        return (
          <TouchableHighlight underlayColor="transparent" onPress={this.press}>
            {this._renderIcon()}
          </TouchableHighlight>
        );
      }
    
}

// 继承
class CloseIcon extends Icons {
    _renderIcon() {
        return (
            <Image source={require('../images/Delete-48.png')} style={styles.icon} />
        );  
      }
}
class SearchIcon extends Icons {
    _renderIcon() {
        return (
            <Image source={require('../images/Search-50.png')} style={styles.icon} />
        );  
      }
}

// 导出
module.exports = {
    CloseIcon,
    SearchIcon,    
};

而我们则可以在页面中这样使用

import {CloseIcon,SearchIcon} from '../style/icon';

...

render() {
	return(
		//... some code
		<CloseIcon></CloseIcon>
	);
}

8.构建一个导航条

当然制作App中,我们经常会遇到制作导航条的要求,

大家可以使用react-native-navbar,自己写也非常简单,样式大致就这些:

	navBar: {
        height: 44,
        flexDirection: 'row',
        justifyContent: 'space-between',
        alignItems: 'stretch',
        backgroundColor:'#fff'
    },
    customTitle: {
        position: 'absolute',
        left: 0,
        right: 0,
        bottom: 7,
        alignItems: 'center',
    },
    navBarButtonContainer: {
        flexDirection: 'row',
        justifyContent: 'center',
        alignItems: 'stretch',
    },
    navBarButton: {
        flexDirection: 'row',
        justifyContent: 'center',
        alignItems: 'center',
    },
    navBarButtonText: {
        fontSize: 17,
        letterSpacing: 0.5,
    },
    navBarTitleContainer: {
        position: 'absolute',
        left: 0,
        right: 0,
        top: 0,
        bottom: 0,
        justifyContent: 'center',
        alignItems: 'center',
    },
    navBarTitleText: {
        fontSize: 17,
        color: '#333',
        fontWeight: '500',
    }

用法如下:

<View style={[styles.navBar,{backgroundColor: '#9b59b6'}]}>
    <View style={styles.navBarTitleContainer}>
        <Text style={[styles.navBarTitleText,{color: '#fff'}]}>NavBar3</Text>
    </View>

    <View style={[styles.navBarButtonContainer,{marginLeft:8}]}>
        <TouchableOpacity style={styles.navBarButton}>
            <View>
                <CloseIcon></CloseIcon>
            </View>
        </TouchableOpacity>
    </View>
    <View style={[styles.navBarButtonContainer,{marginRight:8}]}>
        <TouchableOpacity style={styles.navBarButton}>
            <View>
                <Text style={[styles.navBarButtonText,{color: '#fff'}]}>Done</Text>
            </View>
        </TouchableOpacity>
    </View>
</View>

需要注意,如果设置顶部导航条,记得还有状态栏的高度要算进去,一般设置都为22

9.结合 Redux

想了想做个 App,有下面几个就可以了,界面不low, 数据支撑,用户响应即可。但是我们在做的时候Css和Html确实解决了Bo主不会写界面的问题,但是后面两个咋个办呢?于是乎官方推出了一个新的工具Redux
精炼一点就是Redux就是去去管理页面的状态(用户响应)及数据(接口数据相关)。Redux中强调了三点:

  • 单一数据源
  • State 是只读的
  • 使用纯函数来执行修改

而且Redux支持服务端,这样更加方便我们在进行异步的远程数据获取的实现。

一个简单的使用Demo

10.合理的使用第三方插件

尽管React Native 正式发布的时间还不算非常长,但是npm上已经拥有了大量的第三方类库,因此我们在遇到问题或者强调快速开发的时候我们可以去第三方网react.parts站寻找更好的组件。自己觉得常用的一些如下:

11.全屏图片

可能大家经常会遇到制作landing 页面,这个时候很多时候都是一个全屏的入场的图片,这个时候我们就需要设置好图片的样式。

fullImage:{
    flex:1,
    resizeMode:'cover',
    
}

render() {
    return(
        <Image style={styles.fullImage}   source={require('...')}/>
    );
}

一般我们都设置iphone6 的全屏大小为750 * 1334 保存为xx@2x.png 而6 plus一般是1225 * 2001 命名成xx@3x.png就行,这样不同的型号会去寻找对应的图片。

iPhone各型号分辨率一览。

12.调试

除了开发外,我们还希望能够很好的调试我们的App.默认的话,就像我们调试我们的web页面一样,我们可以用常用的console.log,console.error,console.warn,由于支持chrome调试,我们可以在控制台看到打印的数据。当然,我们也可以真机调试,比如连上你的iPhone,需要注意的是:

你需要修改调试js的地址,在AppDelegate.m中将"localhost"改成你电脑的ip就可以了。

选中你的iPhone就可以调试了。

当然我会持续更新,也欢迎大家pr,项目地址

最后安利一个ppt https://yunpan.cn/cqKEvrPXAS3gy (提取码:0375)