前言
- 本文
有配套視頻,可以酌情觀看。 - 文中內(nèi)容因各人理解不同,可能會(huì)有所偏差,歡迎朋友們聯(lián)系我討論。
- 文中所有內(nèi)容僅供學(xué)習(xí)交流之用,不可用于商業(yè)用途,如因此引起的相關(guān)法律法規(guī)責(zé)任,與我無(wú)關(guān),如文中內(nèi)容對(duì)您造成不便,煩請(qǐng)聯(lián)系 277511806@qq.com 處理,謝謝。
- 轉(zhuǎn)載麻煩注明出處,謝謝。
本篇資源:鏈接: https://pan.baidu.com/s/1qXH2qx2 密碼: b5sj
源碼托管到 github 上,需要源碼的 點(diǎn)我下載,喜歡的話記得 Star,謝謝!
本章許多內(nèi)容本來(lái)是要放到后面講的,考慮到有朋友可能不需要了解
redux相關(guān)內(nèi)容,所以把能想到的一些常見(jiàn)的東西先講下。
小時(shí)風(fēng)云榜按鈕處理
在服務(wù)器返回給我們的
json數(shù)據(jù)中,提供了hasnexthour字段,當(dāng)這個(gè)字段返回為1的時(shí)候,表示后面還有內(nèi)容,按鈕可以點(diǎn)擊,否則不能點(diǎn)擊,按照這個(gè)思路,我們就來(lái)完成這個(gè)功能。-
在
state中新增isNextTouch狀態(tài)isNextTouch:false // 下一小時(shí)按鈕狀態(tài) -
在每次請(qǐng)求成功后都更新下?tīng)顟B(tài):
let isNextTouch = true; if (responseData.hasnexthour == 1) { // hasnexthour不為0時(shí) 下一小時(shí) 按鈕可點(diǎn)擊 isNextTouch = false; } // 重新渲染 this.setState({ dataSource: this.state.dataSource.cloneWithRows(responseData.data), loaded:true, prompt:responseData.displaydate + responseData.rankhour + '點(diǎn)檔' + '(' + responseData.rankduring + ')', isNextTouch:isNextTouch, // 更新按鈕狀態(tài) }); -
接著我們就可以根據(jù)狀態(tài)進(jìn)行相應(yīng)更改:
{/* 下一小時(shí)按鈕 */} <TouchableOpacity onPress={() => this.nextHour()} disabled={this.state.isNextTouch} > <Text style={{marginLeft:10, fontSize:17, color:this.state.isNextTouch == false ? 'green' : 'gray'}}>{"下1小時(shí)" + " >"}</Text> </TouchableOpacity>

ref 和 setNativeProp 的使用
使用
ref可以獲取到相應(yīng)的組件,更加靈活地在需要的地方使用,配合setNativeProps可以做到不直接調(diào)用render直接渲染組件,在某些頻繁更新的組件上使用,可以大大提高性能,不過(guò)千萬(wàn)不要為了使用setNativeProps而使用,某些情況下可能會(huì)適得其反。-
使用方法如下:
test() { this.refs.testText.setNativeProps({ style: { backgroundColor:'green' } }) } <Text ref="testText">快,說(shuō)我?guī)?lt;/Text>
監(jiān)聽(tīng) TabBarItem 點(diǎn)擊與傳值實(shí)現(xiàn) 點(diǎn)擊 Item 進(jìn)行刷新功能
原版
APP中當(dāng)我們點(diǎn)擊首頁(yè)和海淘2個(gè)Item時(shí),會(huì)馬上獲取最新數(shù)據(jù)個(gè)數(shù)然后進(jìn)行更新,這邊來(lái)實(shí)現(xiàn)一下這個(gè)功能。-
通過(guò)通知的方式監(jiān)聽(tīng)I(yíng)tem點(diǎn)擊做相應(yīng)的操作,所以我們?cè)谛枰邮胀ㄖ捻?yè)面注冊(cè)一下通知,在需要的地方發(fā)送通知即可:
- 在
home界面注冊(cè)通知
// 組件加載完成 componentDidMount() { // 注冊(cè)通知 this.subscription = DeviceEventEmitter.addListener('clickHomeItem', () => this.clickTabBarItem()); }- 在頁(yè)面銷毀之前記得注銷下:
componentWillUnmount() { // 注銷通知 this.subscription.remove(); }-
clickTabBarItem方法邏輯:
// 點(diǎn)擊了Item clickTabBarItem() { // 加載最新數(shù)據(jù) this.loadData(); } - 在
-
回到
Main頁(yè)面,我們修改下點(diǎn)擊Item響應(yīng)的事件:- 修改
Item點(diǎn)擊事件:
onPress={() => this.clickItem(selectedTab, subscription)}>-
clickItem方法邏輯:
// 點(diǎn)擊了Item clickItem(selectedTab, subscription) { if (subscription !== "" && this.state.selectedTab == selectedTab) { // 發(fā)送通知 DeviceEventEmitter.emit(subscription); } // 渲染頁(yè)面 this.setState({ selectedTab: selectedTab }) }所以傳值也需要新增
subscription參數(shù),不需要訂閱的按鈕就可以傳""即可。海淘也是類似操作,這邊就不贅述,自己試著實(shí)現(xiàn)一下。
- 修改
每次點(diǎn)擊 Item 獲取到最新數(shù)據(jù)后我們需要及時(shí)更新 Item 角標(biāo)
-
實(shí)現(xiàn)思路很簡(jiǎn)單,我們使用逆?zhèn)鞯姆绞剑看潍@取到最新數(shù)據(jù)的時(shí)候,同時(shí)需要調(diào)用一下 在
main中獲取最新數(shù)據(jù)個(gè)數(shù)的請(qǐng)求方法即可。-
首先我們?cè)?
home定義一個(gè)屬性供外界使用:static defaultProps = { loadDataNumber:{}, // 回調(diào) }; -
接著,當(dāng)我們請(qǐng)求到最新數(shù)據(jù)的同時(shí),我們調(diào)用一下這個(gè)屬性,就可以了:
// 獲取最新數(shù)據(jù)個(gè)數(shù) this.loadDataNumber(); // loadDataNumber 中的邏輯 loadDataNumber() { // 調(diào)用 this.props.loadDataNumber 中保存的代碼塊 this.props.loadDataNumber(); } -
為了方便調(diào)用,我們將
獲取最新數(shù)據(jù)個(gè)數(shù)的邏輯抽出來(lái)放到單獨(dú)的方法內(nèi),這邊順便再介紹AsyncStorage怎么同時(shí)獲取多個(gè)key值的方法:// 獲取最新數(shù)據(jù)個(gè)數(shù)網(wǎng)絡(luò)請(qǐng)求 loadDataNumber() { // 取出id AsyncStorage.multiGet(['cnfirstID', 'usfirstID'], (error, stores) => { // 拼接參數(shù) let params = { "cnmaxid" : stores[0][1], "usmaxid" : stores[1][1], }; // 請(qǐng)求數(shù)據(jù) HTTPBase.get('http://guangdiu.com/api/getnewitemcount.php', params) .then((responseData) => { this.setState({ cnbadgeText:responseData.cn, usbadgeText:responseData.us }) }) .catch((error) => { }) }); } -
很好,接著我們轉(zhuǎn)到 main 中,修改下
renderTabBarItem方法中的內(nèi)容,實(shí)現(xiàn)一下 `` 屬性的方法:renderScene={(route, navigator) => { let Component = route.component; return <Component {...route.params} navigator={navigator} loadDataNumber={() => this.loadDataNumber()} /> }} 到這里就完成了,海淘頁(yè)面也是類似操作,自己試著實(shí)現(xiàn)一下。
-
一鍵置頂功能
一鍵置頂功能也是市面上
APP上可以說(shuō)必備功能了,這邊原版APP也有這個(gè)功能,所以我們跟著來(lái)實(shí)現(xiàn)一下。-
這個(gè)功能實(shí)現(xiàn)更加簡(jiǎn)單,只要我們調(diào)用
ScrollView的scrollTo方法,將y設(shè)置為 0 即可;因?yàn)?ListView是在ScrollView上進(jìn)行的二次開(kāi)發(fā),所以它可以使用ScrollView的所有方法:// 點(diǎn)擊了Item clickTabBarItem() { let PullList = this.refs.pullList; // 一鍵置頂 PullList.scrollTo({y:0}); } 這樣我們的一鍵置頂功能就完成了,在需要的頁(yè)面進(jìn)行同樣操作就可以實(shí)現(xiàn)相同功能。
TabBarItem 邏輯完善
那么為了更好的用戶體驗(yàn),我們這邊還需要來(lái)處理一下點(diǎn)擊
TabBarItem的一下細(xì)節(jié),那就是當(dāng)用戶點(diǎn)擊Item時(shí),可能只是單純的想進(jìn)行頁(yè)面的切換或者置頂操作,而不想進(jìn)行刷新,那么我們就需要來(lái)判斷一下什么時(shí)候需要刷新,什么時(shí)候需要置頂。那么我們可以通過(guò)判斷
ListView中的Scroll的偏移量來(lái)判斷是否需要進(jìn)行置頂操作,當(dāng)偏移量大于 1 的時(shí)候我們就進(jìn)行置頂操作,否則的話我們就進(jìn)行刷新操作。-
那么問(wèn)題又來(lái)了,當(dāng)我們執(zhí)行刷新操作的時(shí)候,應(yīng)該模擬用戶下拉顯示
滾動(dòng)小菊花來(lái)告訴用戶我們?cè)谶M(jìn)行刷新操作,可是pullList并沒(méi)有提供我們這個(gè)方法怎么辦?那我們就需要分析第三方框架的內(nèi)容來(lái)找方法解決這個(gè)問(wèn)題(具體方法,可以觀看我為各位錄制的視頻),這邊就不多講了,直接上最終代碼:// 點(diǎn)擊了Item clickTabBarItem() { let PullList = this.refs.pullList; if (PullList.scroll.scrollProperties.offset > 0) { // 不在頂部 // 一鍵置頂 PullList.scrollTo({y:0}); }else { // 在頂部 // 執(zhí)行下拉刷新動(dòng)畫 PullList.state.pullPan = new Animated.ValueXY({x: 0, y: this.topIndicatorHeight * -1}); // 加載最新數(shù)據(jù) this.loadData(); // 關(guān)閉動(dòng)畫 setTimeout(() => { PullList.resetDefaultXYHandler(); },1000); } }

關(guān)閉篩選菜單滑動(dòng)手勢(shì)
-
那這邊我們的篩選菜單還有個(gè)問(wèn)題,就是可以響應(yīng)我們的手勢(shì)進(jìn)行滾動(dòng),這樣肯定是不對(duì)的,那么我們需要關(guān)閉這個(gè)手勢(shì)的監(jiān)聽(tīng),使這個(gè)菜單不能滾動(dòng),具體操作如下:
{/* 菜單內(nèi)容 */} <ListView scrollEnabled={false} // 關(guān)閉滑動(dòng)功能 dataSource={this.state.dataSource} // 設(shè)置數(shù)據(jù)源 renderRow={this.renderRow.bind(this)} // 根據(jù)數(shù)據(jù)初始化 Cell contentContainerStyle={styles.contentViewStyle} // 樣式 initialListSize={16} // 一次性渲染幾行數(shù)據(jù) />

Navigator 掉幀卡頓問(wèn)題處理
-
到現(xiàn)在肯定有很多朋友發(fā)現(xiàn)
Navigator跳轉(zhuǎn)動(dòng)畫并不是那么流暢,會(huì)出現(xiàn)掉幀卡頓的現(xiàn)象,并不像NavigatorIOS那么絲絲順滑;造成這個(gè)的原因是因?yàn)?NavigatorIOS是在 UI線程 執(zhí)行的 動(dòng)畫操作,而Navigator是在 JS線程執(zhí)行的動(dòng)畫,那這樣就會(huì) 阻塞住 JS線程,那么怎么去解決這個(gè)問(wèn)題?這邊提供 2 種方案:第一種:使用 navigation 框架,這個(gè)是目前替代 navigator 最好的方案之一,很強(qiáng)大,很流暢,但是需要再去學(xué)習(xí)一下使用。
-
第二種:如果你懶得學(xué)習(xí)上面的框架,那么這邊再給各位提供另一種方法 —— 使用官方提供的 API:InteractionManager(可以將一些耗時(shí)較長(zhǎng)的工作安排到所有互動(dòng)或動(dòng)畫完成之后再進(jìn)行。這樣可以保證JavaScript動(dòng)畫的流暢運(yùn)行),這邊我們就使用這種方案來(lái)進(jìn)行一下優(yōu)化:
InteractionManager.runAfterInteractions(() => { this.props.navigator.push({ component: Search, }); });
是的,就這一步操作即可,在其他需要用到 跳轉(zhuǎn)功能的地方 使用一下這個(gè)API即可。

怎樣調(diào)用框架中沒(méi)有提供我們使用的接口
- 這個(gè)篇幅較大,感興趣的朋友還是參考錄制的視頻吧。
removeClippedSubviews
用于提升大列表的滾動(dòng)性能。需要給行容器添加樣式overflow:'hidden'。(Android已默認(rèn)添加此樣式)此屬性默認(rèn)開(kāi)啟
這個(gè)屬性是因?yàn)樵谠缙?
ListView在數(shù)據(jù)到達(dá)一定程度的時(shí)候就會(huì)越來(lái)越卡,最終導(dǎo)致 APP 崩潰退出,使用這個(gè)屬性后 APP 崩潰確實(shí)在一定程度上得到緩解,但是卡頓問(wèn)題還是依舊存在。那等到后面我們會(huì)介紹FlatList,它將是未來(lái)ListView替代品,主要解決它性能差的,占用內(nèi)容持續(xù)增加的問(wèn)題,目前還沒(méi)發(fā)布穩(wěn)定版本,但是經(jīng)過(guò)一段時(shí)間測(cè)試,我覺(jué)得已經(jīng)可以向大家推薦了,所以在后面的章節(jié)中會(huì)為各位介紹的。-
廢了這么多話,這邊我們就先來(lái)使用一下
removeClippedSubviews,很簡(jiǎn)單,使用它只需要在我們封裝的 cell 中的container樣式中添加overflow:'hidden'即可。container: { flexDirection:'row', alignItems:'center', justifyContent:'space-between', backgroundColor:'white', height:120, width:width, borderBottomWidth:0.5, borderBottomColor:'gray', marginLeft:15, overflow:'hidden', },
modal放置的順序
-
這邊我們?cè)嚵讼掳沧浚l(fā)現(xiàn)當(dāng)我們顯示 modal 然后又關(guān)閉 modal 的時(shí)候,就會(huì)出現(xiàn) ListView 列表消失的問(wèn)題,那么其實(shí)是因?yàn)槲覀?modal 放置的順序問(wèn)題,modal 應(yīng)當(dāng)放置到所有主視圖之后創(chuàng)建,避免它影響其他視圖顯示,這邊就以
home頁(yè)面為例,其他視圖自己實(shí)現(xiàn)哈:render() { return ( <View style={styles.container}> {/* 導(dǎo)航欄樣式 */} <CommunalNavBar leftItem = {() => this.renderLeftItem()} titleItem = {() => this.renderTitleItem()} rightItem = {() => this.renderRightItem()} /> {/* 根據(jù)網(wǎng)絡(luò)狀態(tài)決定是否渲染 listview */} {this.renderListView()} {/* 初始化近半小時(shí)熱門 */} <Modal pointerEvents={'box-none'} animationType='slide' transparent={false} visible={this.state.isHalfHourHotModal} onRequestClose={() => this.onRequestClose()} > {/* 包裝導(dǎo)航功能 */} <Navigator initialRoute={{ name:'halfHourHot', component:HalfHourHot }} renderScene={(route, navigator) => { let Component = route.component; return <Component removeModal={(data) => this.closeModal(data)} {...route.params} navigator={navigator} /> }} /> </Modal> {/* 初始化篩選菜單 */} <Modal pointerEvents={'box-none'} animationType='none' transparent={true} visible={this.state.isSiftModal} onRequestClose={() => this.onRequestClose()} > <CommunalSiftMenu removeModal={(data) => this.closeModal(data)} data={HomeSiftData} loadSiftData={(mall, cate) => this.loadSiftData(mall, cate)} /> </Modal> </View> ); } 原因我們之后會(huì)帶大家來(lái)自己 開(kāi)發(fā)一個(gè)類似 modal 的組件,到時(shí)候再跟大家詳解。

Android 加載git圖\動(dòng)圖
細(xì)心的朋友應(yīng)該發(fā)現(xiàn)了一個(gè)問(wèn)題,同樣一張 git 圖片,在 iOS 上可以正常加載,在 Android 上圖片竟然不能動(dòng),可能會(huì)想算了吧,能顯示圖片就行了?轉(zhuǎn)頭發(fā)現(xiàn),產(chǎn)品經(jīng)理正 “悠閑” 磨刀呢。。。那其實(shí)解決這個(gè)問(wèn)題很簡(jiǎn)單,我們只需要使用一下 facebokk 的一個(gè)強(qiáng)大的圖片加載庫(kù)就能解決這個(gè)問(wèn)題了。
-
首先,我們打開(kāi)
build.gradle,在dependencies中添加下面一行代碼compile "com.facebook.fresco:animated-gif:0.13.0" 重新 run 一下,編譯器會(huì)自動(dòng)幫我們添加這個(gè)庫(kù)并配置完畢,那么 Android 上也可以愉快地顯示 gif圖片 了。
什么?找不到這個(gè)文件?那可不行,看一下給各位錄制的視頻吧。

導(dǎo)航欄返回按鈕
-
我們導(dǎo)航欄的返回按鈕很挫對(duì)吧,這邊統(tǒng)一簡(jiǎn)單先改一下:
// 返回左邊按鈕 renderLeftItem() { return( <TouchableOpacity onPress={() => {this.pop()}} > <View style={{flexDirection:'row', alignItems:'center'}}> <Image source={{uri:'back'}} style={styles.navbarLeftItemStyle} /> <Text>返回</Text> </View> </TouchableOpacity> ); }

去除 Android 中輸入框的下劃線
-
那么 Android 中的
TextInput的下劃線是不是丑爆了?這邊我們也來(lái)處理下它,直接使用underlineColorAndroid這個(gè)屬性,讓他為透明即可。underlineColorAndroid={'transparent'}

navigationBar
這邊先來(lái)介紹一下
navigationBa的使用,使用它可以讓我們只在一個(gè)地方管理navigator導(dǎo)航欄的樣式,就不用像現(xiàn)在這樣在每個(gè)頁(yè)面都手動(dòng)添加導(dǎo)航欄。這邊先舉個(gè)例子讓大家知道怎么用,考慮到傳值不方便的原因,到講完
redux之后,再一起講這部分內(nèi)容,所以大家只需要了解一下怎么使用就可以了。-
首先,我們來(lái)看下
navigationBar文件內(nèi)的內(nèi)容:let NavigationBarRouteMapper = { LeftButton(route, navigator, index, navState) { if (index > 0) { return ( <TouchableOpacity onPress={() => navigator.pop()} > <Text>返回</Text> </TouchableOpacity> ) } }, RightButton(route, navigator, index, navState) { }, Title(route, navigator, index, navState) { return( <Text>{route.name}</Text> ) }, }; export default ( <Navigator.NavigationBar style={{backgroundColor:'green'}} routeMapper={NavigationBarRouteMapper} /> ) -
接著,我們到
main文件中使用一下這個(gè)navigationBar:navigationBar={NavigationBar}

react-native 開(kāi)發(fā)中你可能需要的一些小玩意
-
撥打電話(真機(jī)測(cè)試,模擬器沒(méi)有打電話功能):
import { Linking } from ‘react-native’; function callPhone() { return Linking.openURL('tel:10086'); } -
獲取視圖組件的 x,y,寬,高,偏移量的值,可以使用 measure 方法:
this.refs.mainView.measure((x, y, width, height, px,py)) => { console.log(width); } 開(kāi)發(fā)中建議先從 iOS 端做起,安卓端適配;當(dāng)然如果公司不是只有你一個(gè)人負(fù)責(zé) react-native 項(xiàng)目,大可不必理會(huì)這條。
-
開(kāi)發(fā)中有些功能在 模擬器 上是無(wú)法測(cè)試的,這時(shí)候需要配合真機(jī)進(jìn)行調(diào)試,下面整理出一些常見(jiàn)的問(wèn)題:
-
當(dāng) Android 提示找不到服務(wù)器時(shí):
確定電腦與我們手機(jī)連接同一個(gè) WiFi 網(wǎng)絡(luò)環(huán)境下。
我們需要打開(kāi) 開(kāi)發(fā)中菜單(搖下手機(jī)) —— —— —— 輸入電腦IP地址(IP地址怎么找?自己搜索吧)—— 退出菜單 ——
-
當(dāng) iOS 提示找不到服務(wù)器時(shí):
- 打開(kāi) Xcode —— AppDelegate.m 文件 —— 更改 jsCodeLocation 中的 localhost 為電腦的IP地址 —— 重新運(yùn)行一遍。
-
優(yōu)化工具介紹與使用
- 本來(lái)要講的,但是發(fā)現(xiàn)現(xiàn)在的項(xiàng)目還沒(méi)出現(xiàn)太明顯性能問(wèn)題,不好測(cè)試,就放到最后面講吧。
第一版完結(jié)
到這里第一個(gè)版本就完結(jié)了,接下來(lái)就要開(kāi)始我們的第二版本的開(kāi)發(fā)了,那么第二個(gè)版本之前大概會(huì)用一篇的內(nèi)容來(lái)主要講下
react怎么結(jié)合redux進(jìn)行開(kāi)發(fā),并做個(gè)小 Demo,讓大家先熟悉一下redux使用,然后就是我們當(dāng)前的項(xiàng)目 轉(zhuǎn)為redux開(kāi)發(fā)了。這邊想知道大家更想知道或者對(duì)于
redux哪里比較不理解的,好跟著改進(jìn)一下,盡量使文章和視頻更易懂。