ReactNative开发过程中的那些磕磕绊绊

近几个迭代版本我们大iOS都是采用react-native开发的,因为技术比较新,开发过程中遇到各种各样的问题再是在所难免的。忙里偷闲,赶紧把遇到的问题及解决方案做个总结,免得日后又忘了。

问题一:

如图:

报错信息.png

不知道大家有没有遇到过类似的问题。上图中的报错我是在3.14.0版本的开发中第一次遇到,老实说这个问题困扰了我一天,我尝试了各种方法,并且利用 Chrome 联机调试也没发现问题的所在。
这个问题发生了三次:

第一次,是在render里的ListView 组件中,只要我添加了ListView就回报错,但是将ListView替换成普通的View就没有问题了,这下好了锁定目标,一定是ListView有问题,哪里没有配置好。

第二次,发生在渲染自定义Cell中,和第一次在一个类中。

第三次,发生在条件判断中。

不要着急,下面听我给你娓娓道来

情况一:

来粘段代码看看:

render() {
        if (this.state.offline) {
            return (
                <GuideOfflineView />
            );
        }
        if (!this.state.data) {
            return (
                <LoadingView />
            );
        }
        return (
            <View style={{ flex: 1 }}>
                <View style={{
                    width: App.Constant.screenWidth,
                    marginTop: 30,
                    marginBottom:20,
                    flexDirection: 'row',
                    alignItems: 'flex-start',
                    justifyContent: 'space-between',
                }}>
                    <TouchableOpacity style={{
                        marginLeft: 20,
                    }}
                        onPress={this.onCloseBtnPress}>
                        <Image source={App.Image.btn.guideListClose} />
                    </TouchableOpacity>
                    <Text style={{
                        fontSize: 17,
                        color: App.Color.darkGray,
                        textAlign: 'center',
                    }}>
                        Title
                </Text>
                    <View style={{
                        marginRight: 20,
                        height: 13,
                        width: 13,
                    }} />
                </View>
                <TableView
                    data={this.state.data}
                    dataSource={this.state.dataSource}
                    requestData={this.loadMoreData}
                />
            </View>
        );
    }

这里是render()页面,里面的TableView是我自定义的ListView组件,为了使代码更清晰,就把他提出来

const TableView = ({
    style,
    dataSource,
    data,
    requestData,
}) => {
    const loadMoreMessage = () => {
        if (data.items.length === data.total) {
            return;
        }
        requestData();
    };
    if (dataSource === undefined || dataSource.getRowCount() === 0) {
        return (
            <View style={{
                flex: 1,
                alignItems: 'center',
                justifyContent: 'center',
            }}>
                <Text style={{
                    alignItems: 'center',
                    justifyContent: 'center',
                    fontSize: 14,
                    color: App.Color.darkGray,
                }}>
                    暂无云导游
                </Text>
            </View>
        );
    } else {
        return <ListView
            opacity={1}
            dataSource={dataSource}
            renderRow={GuideListCell}
            showVerticalScrollIndicator={false}
            enableEmptySections={true}
            onEndReached={() => { loadMoreMessage(); } }
            onEndReachedThreshold={20}
            renderSeparator={(sectionID, rowID) => {
                return (
                    <View
                        key={`${sectionID}-${rowID}`}
                        style={{
                            height: 0,
                            width: App.Constant.screenWidth,
                            marginTop: 20,
                        }} />
                );
            } }
            />;
    }
};

在ListView中配置了 datasource 和自定义 cell,经验告诉我这里ListView出问题不是datasource有问题就是自定义cell又问题。

第一步:检查dataSource

updateDataSource(data) {
        if (data !== undefined) {
            let newData;
            if (this.state.data !== undefined) {
                newData = this.state.data;
                newData.items = this.state.data.items.concat(data.items);
            } else {
                newData = data;
            }
            var ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
            this.setState({
                data: newData,
                dataSource: ds.cloneWithRows(newData.items),
            });
        }
    }

在我请求完数据后对 ListView 的 datasource 也按要求进行了配置。

这里我想稍微提一下:ListView 对它的 datasource 是有严格格式要求的,首先 datasource 必须是数组,这样它才能根据不同的 rowID 来取出对应数据进行渲染。其次,想要 ListView 使用 datasource 必须要经过 ds.cloneWithRow(dataSource) 操作,这步操作主要是为了提取新数据并进行逐行进行比较,这样ListView就知道哪些行需要重新渲染。

来打印一下数据看ListView的dataSource对不对

'--->> dataSource', { _rowHasChanged: [Function: rowHasChanged],
  _getRowData: [Function: defaultGetRowData],
  _sectionHeaderHasChanged: [Function],
  _getSectionHeaderData: [Function: defaultGetSectionHeaderData],
  _dataBlob: 
   { s1: 
      [ 
        { _id: '36398d0d3c3a43cb86473f411eb4094a',
          name: '台东区',
          weight: 12,
          items: 
           [ { _id: '706b5c36479742308603788a74b6781c',
               location: { lat: 0, lng: 0 },
               cover: { source: 'https://image-cdn.fishsaying.com/89be2d8757cf47dbb1152abb08d765b6.jpg' },
               scenic_id: '36398d0d3c3a43cb86473f411eb4094a',
               title: '东京台东区 大和风骨',
               weight: 12 } ] },
      
        { _id: '9de32b5fffd7451883ff16e0905bb8e3',
          name: '涩谷',
          weight: 14,
          items: 
           [ { _id: 'd643b686ad54426dbd22120861636dd2',
               location: { lat: 0, lng: 0 },
               cover: { source: 'https://image-cdn.fishsaying.com/a9f971ef9d7647a8aa7586431e7db972.jpg' },
               scenic_id: '9de32b5fffd7451883ff16e0905bb8e3',
               title: '东京涩谷区 时尚前沿',
               weight: 14 } ] },
        { _id: '1c6eeeaa96f647e9b3afc9456ccb4e6e',
          name: '新宿',
          weight: 15,
          items: 
           [ { _id: '0ae445cf20424c08ac12e03c500a7a46',
               location: { lat: 0, lng: 0 },
               cover: { source: 'https://image-cdn.fishsaying.com/d872e0876ae44481a391c17bbac76515.jpg' },
               scenic_id: '1c6eeeaa96f647e9b3afc9456ccb4e6e',
               title: '东京新宿区 都市传说',
               weight: 15 } ] },
        { _id: '54cdcbb39a0b8ad439d0605c',
          name: '塔尔寺景区',
          weight: 21,
          items: 
           [ { _id: '109e76e39d6b428a9e5c93cdc11f3367',
               location: { lat: 0, lng: 0 },
               cover: { source: 'https://image-cdn.fishsaying.com/485283515ff346119903fc0d000050eb.jpg' },
               scenic_id: '54cdcbb39a0b8ad439d0605c',
               title: '塔尔寺云导游',
               weight: 21 } ] } ] },
  _dirtyRows: 
   [ [ true,
       true,
       true,
       true ] ],
  _dirtySections: [ true ],
  _cachedRowCount: 4,
  rowIdentities: [ [ '0', '1', '2', '3' ] ],
  sectionIdentities: [ 's1' ] }

一切正常,格式标准,说明打dataSource是没有问题的。

排除dataSource出错。

第二步:检查renderRow中配置的Cell

这里我所传进去的是自定义的Cell --> GuideListCell

import React from 'react'; // eslint-disable-line no-unused-vars
import {
    View,
    StyleSheet,
} from 'react-native';

import App from '.././helper/app.js';
import CloudGuideContainer from './CloudGuideContainer.js';

const Constants = {
  height: App.Constant.screenWidth * 0.58,
  width: App.Constant.screenWidth - 40,
};

const GuideListCell = (rowData, sectionID, rowID) => {
   const name = rowData.name;
   return (
     <View style = {styles.container}>
        <Text style = {styles.title}>
        {name}
        </Text>
      <View style = {styles.listContainer}>
        <CloudGuideContainer dataSource = {rowData} />
      </View>
     </View>
);
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'flex-start',
    alignItems: 'center',
    width:Constants.width,
    height:Constants.height,
    marginLeft:20,
  },
  listContainer: {
    marginTop:20,
    width:Constants.width,
    height:Constants.height,
  },
  title: {
    width:Constants.width,
    fontSize: 14,
    color: App.Color.darkGray,
  }
});

export default GuideListCell;

看起来也没有啥问题呀,reload 一下还是报同样的错误,Chrome调试断点又不能在return的组件里面打,最后会crash在react里面的js文件中这还怎么玩?

Chrome调试报错信息.png

无奈之下我用一个最笨的办法来找报错原因,把return方法中的所有代码都注释掉,只留一个<View />给这个View一个背景色,果然这样ListView出来了,不报错了找这样的方法,一个控件一个控件的打开。几分钟的时间就找到问题了 —— Text,你就是罪魁祸首。可为什么呢,于是我从第一行开始重新浏览代码,结果让我恨毒了自己

import {
    View,
    StyleSheet,
} from 'react-native';

这里面忘了import Text 了

So,不要对Rect的报错机制有太大幻想,的确有时他会报错说没有找个这个组件或者变量,但有的时候人家就会给你报些摸不着头脑的错误信息。

自定义Cell出了问题,这个猜测是对的。

情况二:

还是自定义GuideListCell中。正在我为了找到问题原因并顺利解决洋洋得意的时候,再次请求时,同样的错误又发生了,不同的只是这次报错的ID和上次不一样,但错误格式还是一毛一样。但刚才分明是好了的,界面完完整整的展现在我面前呀。于是我坚信这次不该是我的问题,一定是数据问题,再次打印请求结果发现,后台修改了返回数据,删除了name字段,但是我依然还在取rowData.name 字段,这时,name字段已经不存在了,当然会报错。这个锅后台Java大叔义不容辞的给背了。

情况三:

粘段代码先,

return (
            <View>         
                {this.state.showEmptyView ?
                    <EmptyView/>
                    :
                    <View style={styles.listViewContainer}>
                   this.state.dataSource &&
                    <ListView
                    opacity={1}
                    dataSource={this.state.dataSource}
                    renderRow={ExplicitGuideCell.bind(null,this.updateDownloadStatus)}
                    showVerticalScrollIndicator={false}
                    enableEmptySections={true}
                    onEndReached={this.loadMoreData}
                    onEndReachedThreshold={20}
                    renderSeparator={(sectionID, rowID) => {
                        return (
                            <View
                                key={`${sectionID}-${rowID}`}
                                style={{
                                    height: 0,
                                    width: App.Constant.screenWidth,
                                    marginTop: 10,
                                }} />
                        );
                    } }
                    />
                </View>
                }
                </View>
                </Image>
            </View>
        );

这段代码逻辑很简单,就是如果请求回来的数据为空时,显示空页面,否则,判断this.state.datasource 是否存在,存在就显示ListView。
reload 一下,又是那个熟悉的红色界面,背心一阵寒意,这么简单的界面怎么又错了,积累了前两次的经验,我把import中的组件都检查了一遍,用到的都导入了,数据请求成功后也给this.state 设置了数据,为什么还报错呢?
的确,这里有两层条件判断,一个疏忽就会出问题,所以我还是不建议在return 组件中进行太复杂的逻辑判断。错误原因竟是一对花括号

二层逻辑判断 this.state.dataSource && 前后忘记了花括号。

综上所述,不难看出以上三种情况都报了同样的错误,不同的只是ID。虽然错误信息让你迷茫,但这三种情况都出现在渲染界面的时候,所以均与return()方法相关。

以后若是在遇到这种错误,不妨多在渲染界面的方法中找找原因吧!

问题二:

有这样一个界面

列表.png

一个列表中的cell里面又是一个列表,并且里层的列表可以向左滑动浏览。
我的想法就是,外面一个ListView,在这个ListView的Cell里面又是一个横向滑动的ListView。找到思路了那就开工吧!

结果是在 react-native 中不能直接嵌套使用 ListView,也就是说,不可以在首层的ListView的Cell 中直接再嵌套一个 ListView,就像这样

const GuideListCell = (rowData, sectionID, rowID) => {
   const name = rowData.name;
   return (
     <View style = {styles.container}>
        <Text style = {styles.title}>
        {name}
        </Text>
      <View style = {styles.listContainer}>
      <ListVeew 
       dataSource = {rowData}
       renderRow = {CustomerCell} 
       />
      </View>
     </View>
);
};

亲身试验这样是不行的,具体报错信息搞忘了

解决方案

不能直接嵌套,那我就间接嵌套呗!先用一个View把二层的ListView封装进去,然后在一层的ListView Cell中调用这个空间就行啦!

const GuideListCell = (rowData, sectionID, rowID) => {
   const name = rowData.name;
   return (
     <View style = {styles.container}>
        <Text style = {styles.title}>
        {name}
        </Text>
      <View style = {styles.listContainer}>
        <CloudGuideContainer dataSource = {rowData} />
      </View>
     </View>
);
};

其中CloudGuideContainer 就是那个包裹,用来包裹二层ListView

来看看这个包裹的真面目吧!

import React from 'react'; // eslint-disable-line no-unused-vars
import {
  View,
  ListView,
} from 'react-native';

import App from '.././helper/app.js';
import CloudGuideCell from './CloudGuideCell.js';

const Constants = {
  height: App.Constant.screenWidth * 0.4776,
};

const CloudGuideContainer = ({dataSource}) => {
    const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
    const guideDataSource = ds.cloneWithRows(dataSource.items);
    return (
        <ListView
            style = {{overflow: 'visible',}}
            enableEmptySections={true}
            showsHorizontalScrollIndicator={false}
            horizontal={true}
            dataSource={guideDataSource}
            renderRow={CloudGuideCell}
            renderSeparator={(sectionID, rowID) => {
                return (
                    dataSource.items.length > 1 ?
                    <View
                    key={`${sectionID}-${rowID}`}
                    style={{
                        width: 10,
                         height: Constants.height,
                    }} /> : null
                );
                } }
            />
        );
};

export default CloudGuideContainer;

机智如我,哈哈

问题三:

关于this.state,不太了解的建议直接看官方文档

我遇到到问题是这样的,有一个列表数据很多,需要分页请求,因此我在this.state 中声明一个变量 page 初始值为 0 ,每次请求的时候给state的page加一,直到this.state.data.length === this
.state.data.total 的时候停止请求直接return,这个做法看上去是没有的问题的,但是在特殊情况下就回发生错误,比如下图是一个搜索功能,当输入关键字后点击搜索将会请求数据。但是会有这种情况,当我输完关键字后搜索了几页数据后,我点击TextInput,但是没有修改关键字,再次点击搜索这时我的page应该从1开始才对,可实验证明,page会从上一次的计数值往上加1,而不是从1开始。

搜索.jpg

下面粘上部分相关代码

export default class SearchDetail extends React.Component {
    constructor(props){
        super(props);
        this.state = {
            title: props.title,
            keyword: props.keyword,
            contentStr:props.keyword,
            cellType:undefined,
            data: undefined,
            dataSource: undefined,
            showEmptyView:false,
            fetchingData:false,
            page:0,
        };
   }
   
   // 请求数据
     async fetchData (keyword) {
      var urlStr;
      var cell;
      const nextIndex = this.state.page + 1;
      this.setState({
        cellType: cell,
        fetchingData:true,
      });
      try {
            const result = await App.Request.get({
                url: urlStr,
                parameters: {
                    page: nextIndex,
                    limit: App.Constant.limit,
                    keyword:keyword,
                }
            });
            this.updateDataSource(result);
        } catch (error) {
            this.setState({
                data: [],
                offline: true,
                fetchingVoices: false,
            });
        }
    }
    
    // 点击搜索
     submitKeyWords(text) {
        this.setState({
            showEmptyView:false,
            dataSource:undefined,
            data:undefined,,
            page:0
        });
        this.fetchData(text);
    }

我先搜索武侯祠相关数据,请求三页

2017-01-12 14:31:21.210 [info][tid:com.facebook.react.JavaScript] '--->> page', 1
2017-01-12 14:31:21.210172 [5377:3542880] '--->> page', 1
2017-01-12 14:31:25.409 [info][tid:com.facebook.react.JavaScript] '--->> page', 2
2017-01-12 14:31:25.409329 [5377:3542880] '--->> page', 2
2017-01-12 14:31:27.649 [info][tid:com.facebook.react.JavaScript] '--->> page', 3
2017-01-12 14:31:27.652452 [5377:3542880] '--->> page', 3

接着我不修改任何关键字,再次点击搜索

[tid:com.facebook.react.JavaScript] '--->> page', 4
2017-01-12 14:31:57.153670 [5377:3542880] '--->> page', 4

page并没有如我所愿的从1开始。也就是说我在submitKeyWords(text)方法中,在fetchData之前给将state中的page设为0,并没有起作用。说不起作用可能有些不妥,只能说在我用state的page的时候它至少还不为0。因此我推断this.setState方法可能是异步的。于是我google了一下果不其然

State Updates May Be Asynchronous
React may batch multiple setState() calls into a single update for performance.

Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.

文档地址https://facebook.github.io/react/docs/state-and-lifecycle.html

好了,找到问题的原因了,那怎么解决呢?如果在OC中我一定会声明一个属性来做这件事,那不妨也在react中试一试,看是否可行。改一下代码:

export default class SearchDetail extends React.Component {
    constructor(props){
        super(props);
        this.page = 0;
        this.state = {
            title: props.title,
            keyword: props.keyword,
            contentStr:props.keyword,
            cellType:undefined,
            data: undefined,
            dataSource: undefined,
            showEmptyView:false,
            fetchingData:false,
        };
    }
    
  async fetchData (keyword) {
      var urlStr;
      var cell;
      const nextIndex = this.page + 1;
      this.setState({
        cellType: cell,
        fetchingData:true,
      });
      this.page = nextIndex;
      try {
            const result = await App.Request.get({
                url: urlStr,
                parameters: {
                    page: nextIndex,
                    limit: App.Constant.limit,
                    keyword:keyword,
                }
            });
            this.updateDataSource(result);
        } catch (error) {
            this.setState({
                data: [],
                offline: true,
                fetchingVoices: false,
            });
        }
    }
    
  submitKeyWords(text) {
        this.setState({
            showEmptyView:false,
            dataSource:undefined,
            data:undefined,
        });
        this.page = 0;
        this.fetchData(text);
    }

reload 一下,成功!每当我点击搜索按钮page都会先被置为0。

问题四:

你是怎么设施Text组件的背景色透明呢?

一开始我用了一种很笨的方法 backgroundColor: App.Color.white.alpha(0) 简直要被自己蠢哭了

有次无意中看到别人的方法
backgroundColor: 'transparent'
学习了!

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

推荐阅读更多精彩内容

  • 公司打算用react-native开发APP,初始RN遇到了很多坑,搭建了一个小的项目框架,结合redux根据公司...
    45b645c5912e阅读 715评论 0 5
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,050评论 25 707
  • 1.badgeVaule气泡提示 2.git终端命令方法> pwd查看全部 >cd>ls >之后桌面找到文件夹内容...
    i得深刻方得S阅读 4,607评论 1 9
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,495评论 18 139
  • 越是不守规则的人,越喜欢中国这个法制社会。因为中国的法制,其实是向弱者倾斜的弱者有理制度。 在这个体制下,不懂法没...
    maofay阅读 345评论 1 8