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閱讀 229,619評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,155評論 3 425
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,635評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,539評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,255評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,646評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,838評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,399評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,146評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,338評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,565評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,983評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,257評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,059評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,296評論 2 376

推薦閱讀更多精彩內容

  • 公司打算用react-native開發APP,初始RN遇到了很多坑,搭建了一個小的項目框架,結合redux根據公司...
    45b645c5912e閱讀 735評論 0 5
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,701評論 25 708
  • 1.badgeVaule氣泡提示 2.git終端命令方法> pwd查看全部 >cd>ls >之后桌面找到文件夾內容...
    i得深刻方得S閱讀 4,703評論 1 9
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,814評論 18 139
  • 越是不守規則的人,越喜歡中國這個法制社會。因為中國的法制,其實是向弱者傾斜的弱者有理制度。 在這個體制下,不懂法沒...
    maofay閱讀 358評論 1 8