近幾個(gè)迭代版本我們大iOS都是采用react-native開(kāi)發(fā)的壤圃,因?yàn)榧夹g(shù)比較新,開(kāi)發(fā)過(guò)程中遇到各種各樣的問(wèn)題再是在所難免的囤热。忙里偷閑昙啄,趕緊把遇到的問(wèn)題及解決方案做個(gè)總結(jié)穆役,免得日后又忘了。
問(wèn)題一:
如圖:
不知道大家有沒(méi)有遇到過(guò)類(lèi)似的問(wèn)題跟衅。上圖中的報(bào)錯(cuò)我是在3.14.0版本的開(kāi)發(fā)中第一次遇到,老實(shí)說(shuō)這個(gè)問(wèn)題困擾了我一天播歼,我嘗試了各種方法伶跷,并且利用 Chrome 聯(lián)機(jī)調(diào)試也沒(méi)發(fā)現(xiàn)問(wèn)題的所在掰读。
這個(gè)問(wèn)題發(fā)生了三次:
第一次,是在render里的ListView 組件中叭莫,只要我添加了ListView就回報(bào)錯(cuò)蹈集,但是將ListView替換成普通的View就沒(méi)有問(wèn)題了,這下好了鎖定目標(biāo)雇初,一定是ListView有問(wèn)題拢肆,哪里沒(méi)有配置好。
第二次靖诗,發(fā)生在渲染自定義Cell中郭怪,和第一次在一個(gè)類(lèi)中。
第三次刊橘,發(fā)生在條件判斷中鄙才。
不要著急,下面聽(tīng)我給你娓娓道來(lái)
情況一:
來(lái)粘段代碼看看:
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()頁(yè)面促绵,里面的TableView是我自定義的ListView組件攒庵,為了使代碼更清晰,就把他提出來(lái)
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,
}}>
暫無(wú)云導(dǎo)游
</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败晴,經(jīng)驗(yàn)告訴我這里L(fēng)istView出問(wèn)題不是datasource有問(wèn)題就是自定義cell又問(wèn)題浓冒。
第一步:檢查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),
});
}
}
在我請(qǐng)求完數(shù)據(jù)后對(duì) ListView 的 datasource 也按要求進(jìn)行了配置锨能。
這里我想稍微提一下:ListView 對(duì)它的 datasource 是有嚴(yán)格格式要求的算途,首先 datasource 必須是數(shù)組,這樣它才能根據(jù)不同的 rowID 來(lái)取出對(duì)應(yīng)數(shù)據(jù)進(jìn)行渲染必峰。其次糖驴,想要 ListView 使用 datasource 必須要經(jīng)過(guò) ds.cloneWithRow(dataSource) 操作僚祷,這步操作主要是為了提取新數(shù)據(jù)并進(jìn)行逐行進(jìn)行比較,這樣ListView就知道哪些行需要重新渲染贮缕。
來(lái)打印一下數(shù)據(jù)看ListView的dataSource對(duì)不對(duì)
'--->> dataSource', { _rowHasChanged: [Function: rowHasChanged],
_getRowData: [Function: defaultGetRowData],
_sectionHeaderHasChanged: [Function],
_getSectionHeaderData: [Function: defaultGetSectionHeaderData],
_dataBlob:
{ s1:
[
{ _id: '36398d0d3c3a43cb86473f411eb4094a',
name: '臺(tái)東區(qū)',
weight: 12,
items:
[ { _id: '706b5c36479742308603788a74b6781c',
location: { lat: 0, lng: 0 },
cover: { source: 'https://image-cdn.fishsaying.com/89be2d8757cf47dbb1152abb08d765b6.jpg' },
scenic_id: '36398d0d3c3a43cb86473f411eb4094a',
title: '東京臺(tái)東區(qū) 大和風(fēng)骨',
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: '東京澀谷區(qū) 時(shí)尚前沿',
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: '東京新宿區(qū) 都市傳說(shuō)',
weight: 15 } ] },
{ _id: '54cdcbb39a0b8ad439d0605c',
name: '塔爾寺景區(qū)',
weight: 21,
items:
[ { _id: '109e76e39d6b428a9e5c93cdc11f3367',
location: { lat: 0, lng: 0 },
cover: { source: 'https://image-cdn.fishsaying.com/485283515ff346119903fc0d000050eb.jpg' },
scenic_id: '54cdcbb39a0b8ad439d0605c',
title: '塔爾寺云導(dǎo)游',
weight: 21 } ] } ] },
_dirtyRows:
[ [ true,
true,
true,
true ] ],
_dirtySections: [ true ],
_cachedRowCount: 4,
rowIdentities: [ [ '0', '1', '2', '3' ] ],
sectionIdentities: [ 's1' ] }
一切正常辙谜,格式標(biāo)準(zhǔn),說(shuō)明打dataSource是沒(méi)有問(wèn)題的感昼。
排除dataSource出錯(cuò)装哆。
第二步:檢查renderRow中配置的Cell
這里我所傳進(jìn)去的是自定義的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;
看起來(lái)也沒(méi)有啥問(wèn)題呀,reload 一下還是報(bào)同樣的錯(cuò)誤定嗓,Chrome調(diào)試斷點(diǎn)又不能在return的組件里面打蜕琴,最后會(huì)crash在react里面的js文件中這還怎么玩?
無(wú)奈之下我用一個(gè)最笨的辦法來(lái)找報(bào)錯(cuò)原因宵溅,把return方法中的所有代碼都注釋掉凌简,只留一個(gè)<View />給這個(gè)View一個(gè)背景色,果然這樣ListView出來(lái)了恃逻,不報(bào)錯(cuò)了找這樣的方法雏搂,一個(gè)控件一個(gè)控件的打開(kāi)藕施。幾分鐘的時(shí)間就找到問(wèn)題了 —— Text,你就是罪魁禍?zhǔn)淄怪!裳食?蔀槭裁茨兀谑俏覐牡谝恍虚_(kāi)始重新瀏覽代碼芙沥,結(jié)果讓我恨毒了自己
import {
View,
StyleSheet,
} from 'react-native';
這里面忘了import Text 了
So诲祸,不要對(duì)Rect的報(bào)錯(cuò)機(jī)制有太大幻想,的確有時(shí)他會(huì)報(bào)錯(cuò)說(shuō)沒(méi)有找個(gè)這個(gè)組件或者變量而昨,但有的時(shí)候人家就會(huì)給你報(bào)些摸不著頭腦的錯(cuò)誤信息救氯。
自定義Cell出了問(wèn)題,這個(gè)猜測(cè)是對(duì)的配紫。
情況二:
還是自定義GuideListCell中径密。正在我為了找到問(wèn)題原因并順利解決洋洋得意的時(shí)候,再次請(qǐng)求時(shí)躺孝,同樣的錯(cuò)誤又發(fā)生了享扔,不同的只是這次報(bào)錯(cuò)的ID和上次不一樣,但錯(cuò)誤格式還是一毛一樣植袍。但剛才分明是好了的惧眠,界面完完整整的展現(xiàn)在我面前呀。于是我堅(jiān)信這次不該是我的問(wèn)題于个,一定是數(shù)據(jù)問(wèn)題氛魁,再次打印請(qǐng)求結(jié)果發(fā)現(xiàn),后臺(tái)修改了返回?cái)?shù)據(jù)厅篓,刪除了name字段秀存,但是我依然還在取rowData.name 字段,這時(shí)羽氮,name字段已經(jīng)不存在了或链,當(dāng)然會(huì)報(bào)錯(cuò)。這個(gè)鍋后臺(tái)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>
);
這段代碼邏輯很簡(jiǎn)單,就是如果請(qǐng)求回來(lái)的數(shù)據(jù)為空時(shí)令宿,顯示空頁(yè)面叼耙,否則,判斷this.state.datasource 是否存在粒没,存在就顯示ListView筛婉。
reload 一下,又是那個(gè)熟悉的紅色界面癞松,背心一陣寒意爽撒,這么簡(jiǎn)單的界面怎么又錯(cuò)了冕碟,積累了前兩次的經(jīng)驗(yàn),我把import中的組件都檢查了一遍匆浙,用到的都導(dǎo)入了,數(shù)據(jù)請(qǐng)求成功后也給this.state 設(shè)置了數(shù)據(jù)厕妖,為什么還報(bào)錯(cuò)呢首尼?
的確,這里有兩層條件判斷言秸,一個(gè)疏忽就會(huì)出問(wèn)題软能,所以我還是不建議在return 組件中進(jìn)行太復(fù)雜的邏輯判斷。錯(cuò)誤原因竟是一對(duì)花括號(hào)
二層邏輯判斷 this.state.dataSource &&
前后忘記了花括號(hào)举畸。
綜上所述查排,不難看出以上三種情況都報(bào)了同樣的錯(cuò)誤,不同的只是ID抄沮。雖然錯(cuò)誤信息讓你迷茫跋核,但這三種情況都出現(xiàn)在渲染界面的時(shí)候,所以均與return()方法相關(guān)叛买。
以后若是在遇到這種錯(cuò)誤砂代,不妨多在渲染界面的方法中找找原因吧!
問(wèn)題二:
有這樣一個(gè)界面
一個(gè)列表中的cell里面又是一個(gè)列表率挣,并且里層的列表可以向左滑動(dòng)瀏覽刻伊。
我的想法就是,外面一個(gè)ListView椒功,在這個(gè)ListView的Cell里面又是一個(gè)橫向滑動(dòng)的ListView捶箱。找到思路了那就開(kāi)工吧!
結(jié)果是在 react-native 中不能直接嵌套使用 ListView动漾,也就是說(shuō)丁屎,不可以在首層的ListView的Cell 中直接再嵌套一個(gè) 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>
);
};
親身試驗(yàn)這樣是不行的,具體報(bào)錯(cuò)信息搞忘了
解決方案
不能直接嵌套谦炬,那我就間接嵌套唄悦屏!先用一個(gè)View把二層的ListView封裝進(jìn)去,然后在一層的ListView Cell中調(diào)用這個(gè)空間就行啦键思!
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 就是那個(gè)包裹础爬,用來(lái)包裹二層ListView
來(lái)看看這個(gè)包裹的真面目吧!
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;
機(jī)智如我吼鳞,哈哈
問(wèn)題三:
關(guān)于this.state,不太了解的建議直接看官方文檔
我遇到到問(wèn)題是這樣的看蚜,有一個(gè)列表數(shù)據(jù)很多,需要分頁(yè)請(qǐng)求赔桌,因此我在this.state 中聲明一個(gè)變量 page 初始值為 0 供炎,每次請(qǐng)求的時(shí)候給state的page加一渴逻,直到this.state.data.length === this
.state.data.total 的時(shí)候停止請(qǐng)求直接return,這個(gè)做法看上去是沒(méi)有的問(wèn)題的音诫,但是在特殊情況下就回發(fā)生錯(cuò)誤惨奕,比如下圖是一個(gè)搜索功能,當(dāng)輸入關(guān)鍵字后點(diǎn)擊搜索將會(huì)請(qǐng)求數(shù)據(jù)竭钝。但是會(huì)有這種情況梨撞,當(dāng)我輸完關(guān)鍵字后搜索了幾頁(yè)數(shù)據(jù)后,我點(diǎn)擊TextInput香罐,但是沒(méi)有修改關(guān)鍵字卧波,再次點(diǎn)擊搜索這時(shí)我的page應(yīng)該從1開(kāi)始才對(duì),可實(shí)驗(yàn)證明庇茫,page會(huì)從上一次的計(jì)數(shù)值往上加1港粱,而不是從1開(kāi)始。
下面粘上部分相關(guān)代碼
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,
};
}
// 請(qǐng)求數(shù)據(jù)
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,
});
}
}
// 點(diǎn)擊搜索
submitKeyWords(text) {
this.setState({
showEmptyView:false,
dataSource:undefined,
data:undefined,,
page:0
});
this.fetchData(text);
}
我先搜索武侯祠相關(guān)數(shù)據(jù)旦签,請(qǐng)求三頁(yè)
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
接著我不修改任何關(guān)鍵字查坪,再次點(diǎn)擊搜索
[tid:com.facebook.react.JavaScript] '--->> page', 4
2017-01-12 14:31:57.153670 [5377:3542880] '--->> page', 4
page并沒(méi)有如我所愿的從1開(kāi)始。也就是說(shuō)我在submitKeyWords(text)方法中宁炫,在fetchData之前給將state中的page設(shè)為0咪惠,并沒(méi)有起作用。說(shuō)不起作用可能有些不妥淋淀,只能說(shuō)在我用state的page的時(shí)候它至少還不為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
好了朵纷,找到問(wèn)題的原因了炭臭,那怎么解決呢?如果在OC中我一定會(huì)聲明一個(gè)屬性來(lái)做這件事袍辞,那不妨也在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 一下搅吁,成功威创!每當(dāng)我點(diǎn)擊搜索按鈕page都會(huì)先被置為0。
問(wèn)題四:
你是怎么設(shè)施Text組件的背景色透明呢谎懦?
一開(kāi)始我用了一種很笨的方法 backgroundColor: App.Color.white.alpha(0)
簡(jiǎn)直要被自己蠢哭了
有次無(wú)意中看到別人的方法
backgroundColor: 'transparent'
學(xué)習(xí)了!