ReactNative常见问题

最近看到了一篇关于ReactNative常见问题的博客。正好前段时间遇到过其中一些问题,感觉总结的挺好的,转载过来以备用。^_^

一般问题

RN和React.js是一个东西吗?

RN和React.js共用一些抽象层,但具体有很多差异,且目标平台不同:
RN目前只能开发iOS/Android App,而React.js用于开发web页面。

RN有哪些已经上架的案例?

官方最近推出了一个由爱好者自行提交的showcase页面

RN可以在windows下开发吗?

对于iOS开发,可以通过虚拟机等方式,但很麻烦也不推荐。做iOS开发,迟早你都需要一台Mac电脑。
对于Android开发,理论上没问题。但由于FB的员工基本都用mac,没有怎么管过windows兼容性,
所以目前的版本可能在windows上会遇到一些问题。

RN所支持的最低iOS和Android版本?

Android >= 4.1 (API 16)
iOS >= 7.0

RN和cordova/phonegap是一个东西吗?

不一样。RN不是一个webview(但包含了webview组件),不能直接复用web页面代码。
RN的性能接近原生,超过cordova/phonegap。

可以使用现有的js库吗?

由于RN理论上更接近nodejs的运行环境,所以对nodejs的库兼容更好一些。
浏览器端的js库,涉及到DOM、BOM、CSS等功能的模块无法使用,因为RN的环境中没有这些东西。

可以使用现有的objc/swift/java库吗?

可以,但需要参照这篇和这篇进行修改。

环境搭建与编译问题

创建新项目,react native init xxx命令长时间无响应,或报错shasum check failed

由于众所周知的网络原因,react-native命令行从npm官方源拖代码时会遇上麻烦。请将npm仓库源替换为国内镜像:

npm config set registry https://registry.npm.taobao.org
npm config set disturl https://npm.taobao.org/dist

另,执行init时切记不要在前面加上sudo(否则新项目的目录所有者会变为root而不是当前用户,
导致一系列权限问题,请使用chown修复)。

react-native中文网提供了完整的绿色纯净新项目包。
完整打包全部iOS和Android的第三方依赖,只要环境配置正确,无需科学上网漫长等待,即可直接运行。

报错EACCES: permission denied, open ‘Users/你的用户名/.babel.json’

执行如下命令:

sudo chown 你的用户名 ~/.babel.json

如何升级RN版本?

请用编辑器打开项目目录中的package.json,找到类似下面的一行配置

"react-native": "0.13.0",

将其改为要升级的版本号,如“0.15.0-rc”(当然要先确定这个版本已经发布到npm上了,
如果配置中有^或~之类的符号,可以参考这篇文章来了解其含义。)。

然后在当前目录的命令行中执行npm i

如果提示权限错误则在前面加上sudo(windows下不需要).

npm i执行完毕且成功不报错之后,在项目目录中运行

react-native upgrade

对于0.14以下版本升级0.14的情况,还需要额外手动处理一下。

报错:EMFILE, too many open files ‘……’

请检查node版本(4.0以上),以及是否安装了watchman(目前只有Mac能装这个)

报错:SyntaxError: Use of const in strict mode

请检查node版本(4.0以上)。

Windows下报错:ERROR Watcher took too long to load Try running watchman version from your terminal

启动packager.js时多传一个--nonPersistent参数。

报错:Invariant Violation:Application XXXX has not been registered.

请确保index.ios.js中的

AppRegistry.registerComponent('项目名',() => ...);
与appDelegate.m中的RCTRootView*rootView = [[RCTRootViewalloc]initWithBundleURL:jsCodeLocation

moduleName:@"项目名" launchOptions:launchOptions];
或是MainActivity.java中的
mReactRootView.startReactApplication(mReactInstanceManager, "项目名", null);都保持一致。

应该使用什么IDE开发?

虽然常用的JS编辑器很多,但由于RN大量使用jsx和es6语法,
目前只有sublime text(通过插件)和webstorm(10以上版本)提供了良好的支持。
笔者推荐webstorm,因为它有更完善的语法提示和补全。
另外虽然主要的 业务逻辑是使用js开发,但仍然要依赖于原生的编译/调试环境,所以你还需要同时运行Xcode(iOS)或Android Studio(android)等

开发与调试问题

如何开启调试功能?

点击iOS模拟器顶部的Hardware菜单,选择Shake Gesture(对应真机摇一摇),会自动弹出如下图的菜单。

安卓模拟器则是点击菜单键,真机上没有菜单键的,摇一摇即可。

选 择Debug in Chrome即会启动Chrome作为运行和调试环境(注意此时JS引擎为Chrome的V8,与iOS真机的javascriptCore引擎存在一些 差异)。
选择Inspect Element即可以像调试网页元素一样查看布局元素的样式,但比较简陋。
React Devtools插件可装可不装,它只用来查看布局,不影响调试,且在目前的版本(>0.13)中还无法正常加载。

调试模式下报错:Runtime is not ready. Make sure…或是socket closed.

有时Chrome进程会失去响应,
可以尝试手动将Chrome的React Native Debugger标签切换到前台再Reload模拟器页面。

使用ListView时报错:Sticky header index 0 was outside the range {…}

看起来是个数组越界错误,但多数情况下是由于ListView的子组件渲染错误(如套数据时没有检查undefined等)引起,
而非ListView本身的问题。

ListView的数据到底应该怎么配?

下面是我曾经写的一个小例子:

import React, { Component } from 'react';
import {
    StyleSheet,
    Text,
    View,
    ListView,
    Dimensions,
    Image,
    ActivityIndicator,
    RefreshControl,
    TouchableHighlight,
} from 'react-native';
let Icon = require('react-native-vector-icons/FontAwesome');
let Mock = require('mockjs');
let width = Dimensions.get('window').width;
let height = Dimensions.get('window').height;

class Detail extends Component {
    back(){
           console.log('123')
        this.props.navigator.pop();
    }
    render(){
        return(
            <View>
                <View style={{marginTop:40}}>
                    <Text onPress={this.back.bind(this)}>返回</Text>
                </View>
                <View style={{marginTop:40}}>
                    <Text>{this.props.title}</Text>
                </View>
            </View>
        )
    }
}


class Index extends Component{
    constructor(props){
        super(props)
        ds = new ListView.DataSource({rowHasChanged:(row1,row2)=>row1 != row2});
        this.state={
            dataSource:ds.cloneWithRows([]),
            num:0,
            all:1,
            list:[],
            hasMoreData:true,
            isRefreshing:false,
        };
        this._fetch();
    }
    _fetch(params){
        let argu = params;
        if(this.state.num >= this.state.all){
            this.setState({
                hasMoreData:false
            })
            return
        }
        fetch('http://rap.taobao.org/mockjs/8607/all/haha?reqParam=all')
            .then((response)=>(response.json()))
            .then((responseJson)=>{
                let data = Mock.mock(responseJson);
                if(data.success){
                    if(argu == 'refresh'){
                    this.state.list=data.data.concat(this.state.list);
                        this.setState({
                            num:this.state.num + data.data.length,
                            all:data.totle,

                        dataSource:ds.cloneWithRows(this.state.list)

                        })
                    }else{
                        this.state.list=this.state.list.concat(data.data);
                        this.setState({
                            num:this.state.num + data.data.length,
                            all:data.totle,
                            dataSource:ds.cloneWithRows(this.state.list)

                        })
                    }

                }
            })
        console.log(this.state.list);
    }
    _renderFooter(){
        if(this.state.hasMoreData){
            return(
                <View     style={{justifyContent:'center',alignItems:'center'}}>
                    <ActivityIndicator
                    animating={this.state.animating}
                        style={[styles.centering, {height: 50}]}
                    />
                    <Text style={{color:'#999'}}>努力加载中……</Text>
                </View>
            )
        }else{
            return(
                <View style={{justifyContent:'center',alignItems:'center',marginTop:20}}>
                    <Text style={{color:'#999'}}>没有更多数据了</Text>
                </View>
            )
        }
    }
    switch(rowData){
        this.props.navigator.push({
            name:'详情',
            component:Detail,
            params:rowData,
        })
    }
    _render(rowData){
        return(
            <TouchableHighlight onPress={this.switch.bind(this,rowData)}>
                <View style={{height:height*0.14,borderBottomWidth:1,flexDirection:'row',justifyContent:'space-between'}}>
                    <View style={styles.pic}>
                        <Image
                        style={{width:width*0.3,height:height*0.125}}
                        source={{uri:rowData.pic}}
                        />
                    </View>
                    <View style={styles.content}>
                        <View style={styles.title}>
                            <Text style={{lineHeight:height*0.04,fontSize:12,color:'green'}}>{rowData.title}</Text>
                        </View>
                        <View style={styles.info}>
                            <View style={styles.num}>
                                <Icon
                                style={styles.icon}
                                name="heart"/>
                                <Text style={{color:'gray',fontSize:8}}>{rowData.num}人在学习</Text>
                            </View>
                            <View style={styles.time}>
                                <Icon
                                style={styles.icon}
                                name="heart"/>
                                <Text style={{color:'gray',fontSize:8}}>{rowData.time}小时</Text>
                            </View>
                        </View>
                    </View>
                </View>
            </TouchableHighlight>
        )
    }
    render(){
        return(
            <View style={{height:600,marginTop:20}}>
                <View>
                    <Text>课程列表</Text>
                </View>
                <ListView
                    dataSource={this.state.dataSource}
                    renderRow={this._render.bind(this)}
                enableEmptySections={true}
                    onEndReached={this._fetch.bind(this)}  //滚动条触底时调用模块
                onEndReachedThreshold={20}   //距离底部多少执行触底函数
                renderFooter={this._renderFooter.bind(this)}   //上拉加载
                showsVerticalScrollIndicator={false}
                automaticallyAdjustContentInsets={false}
                    refreshControl={
                        this.state.hasMoreData?
                            <RefreshControl
                                refreshing={this.state.isRefreshing}
                                onRefresh={this._fetch.bind(this,'refresh')}
                            tintColor="#ff0000"
                            title="正在刷新……"
                            titleColor="#00ff00"
                            colors={['#ff0000', '#00ff00', '#0000ff']}
                            progressBackgroundColor="#ffff00"
                            />
                            :
                            <RefreshControl
                            refreshing={this.state.isRefreshing}
                            onRefresh={this._fetch.bind(this,'refresh')}
                                tintColor="#ff0000"
                                title="没有更多数据了,休息一会吧。"
                                titleColor="#00ff00"
                                colors={['#ff0000', '#00ff00', '#0000ff']}
                            progressBackgroundColor="#ffff00"
                            />
                    }   //下拉刷新
                />
            </View>
        )
    }
}
const styles = StyleSheet.create({
    pic:{
        width:width*0.35,
        height:height*0.125,
        padding:5
    },
    content:{
        width:width*0.7,
        height:height*0.125,
    },
    title:{
        marginTop:10,
        height:height*0.04
    },
    info:{
        marginTop:20,
        flexDirection:'row'
    },
    num:{
        flexDirection:'row'
    },
    time:{
        flexDirection:'row',
        marginLeft:20
    },
    icon:{
        color:'lightgreen',
        marginRight:5
    }
})
module.exports = Index;

使用Image时报错:You are trying to render the global Image variable as a React element. You probably forgot to require Image.

由于React的Image组件和全局的Image对象重名,所以使用Image组件时一定要记得在文件开头正确引入React的Image组件。

在使用Navigator的同时使用ListView或ScrollView,后两者的头部会多出一些空间。

将automaticallyAdjustContentInsets属性设为{false}.

有一些示例代码中有奇怪的问号,比如function foo(x:?string),代表什么意思?

这是通过一个名为flow的外部工具为javascript加上强类型检查的功能,不影响编译和运行。

报错:Adjacent JSX elements must be wrapped in an enclosing tag.

render方法中必须只能包含一个根元素

报错:Invariant Violation: onlyChild must be passed a children with exactly one child

一般是Touchable开头的几个组件,如果没有子元素或者指定多个并列子元素都会报错。

如何获取服务器端数据/可以使用Ajax吗?

可以用ajax,以及大部分现有的ajax库,而且不受浏览器跨域限制。
官方推荐用更简单的fetch api来替代传统的ajax.但目前还无法在Chrome中直接观测请求的详情。

有些事情无论你有多着急或者多害怕,我们现在都不能往前冲,冲出去也没有用,飞不起来的,现在你只需要静静地,等风来。 ———— 《等风来》


感谢原文作者:http://blog.sina.com.cn/s/blog_7bee73ed0102vyht.html