React-Native 之 項目實戰(五)


前言


  • 本文 有配套視頻,可以酌情觀看。
  • 文中內容因各人理解不同,可能會有所偏差,歡迎朋友們聯系我討論。
  • 文中所有內容僅供學習交流之用,不可用於商業用途,如因此引起的相關法律法規責任,與我無關,如文中內容對您造成不便,煩請聯系 277511806@qq.com 處理,謝謝。
  • 轉載麻煩注明出處,謝謝。
  • 本篇資源:鏈接: https://pan.baidu.com/s/1qXH2qx2 密碼: b5sj

  • 源碼托管到 github 上,需要源碼的 點我下載,喜歡的話記得 Star,謝謝!

  • 本章許多內容本來是要放到后面講的,考慮到有朋友可能不需要了解 redux 相關內容,所以把能想到的一些常見的東西先講下。

小時風雲榜按鈕處理


  • 在服務器返回給我們的 json 數據中,提供了 hasnexthour 字段,當這個字段返回為 1 的時候,表示后面還有內容,按鈕可以點擊,否則不能點擊,按照這個思路,我們就來完成這個功能。

  • state 中新增 isNextTouch 狀態

      	isNextTouch:false   // 下一小時按鈕狀態
    
  • 在每次請求成功后都更新下狀態:

      	let isNextTouch = true;
    
          if (responseData.hasnexthour == 1) {    // hasnexthour不為0時 下一小時 按鈕可點擊
              isNextTouch = false;
          }
    
          // 重新渲染
          this.setState({
              dataSource: this.state.dataSource.cloneWithRows(responseData.data),
              loaded:true,
              prompt:responseData.displaydate + responseData.rankhour + '點檔' + '(' + responseData.rankduring + ')',
              isNextTouch:isNextTouch,    // 更新按鈕狀態
          });
    
  • 接着我們就可以根據狀態進行相應更改:

      	{/* 下一小時按鈕 */}
        <TouchableOpacity
            onPress={() => this.nextHour()}
            disabled={this.state.isNextTouch}
        >
            <Text style={{marginLeft:10, fontSize:17, color:this.state.isNextTouch == false ? 'green' : 'gray'}}>{"下1小時" + " >"}</Text>
        </TouchableOpacity>
    

小時風雲榜按鈕處理.gif

ref 和 setNativeProp 的使用


  • 使用 ref 可以獲取到相應的組件,更加靈活地在需要的地方使用,配合 setNativeProps 可以做到不直接調用 render 直接渲染組件,在某些頻繁更新的組件上使用,可以 大大提高性能,不過 千萬 不要為了使用 setNativeProps 而使用,某些情況下可能會適得其反。

  • 使用方法如下:

    
      	test() {
      		this.refs.testText.setNativeProps({
      			style: {
      				backgroundColor:'green'
      			}
      		})
      	}
    
      	<Text ref="testText">快,說我帥</Text>
    

監聽 TabBarItem 點擊與傳值實現 點擊 Item 進行刷新功能


  • 原版 APP 中當我們點擊 首頁和海淘 2個 Item 時,會馬上獲取最新數據個數然后進行更新,這邊來實現一下這個功能。

  • 通過通知的方式監聽Item點擊做相應的操作,所以我們在需要接收通知的頁面注冊一下通知,在需要的地方發送通知即可:

    • home 界面注冊通知
      	// 組件加載完成
    componentDidMount() {
        // 注冊通知
        this.subscription = DeviceEventEmitter.addListener('clickHomeItem', () => this.clickTabBarItem());
    }
    
    • 在頁面銷毀之前記得注銷下:
      	componentWillUnmount() {
        // 注銷通知
        this.subscription.remove();
    	}
    
    • clickTabBarItem 方法邏輯:
      	// 點擊了Item
          clickTabBarItem() {
              // 加載最新數據
              this.loadData();
          }
    
  • 回到 Main 頁面,我們修改下點擊 Item 響應的事件:

    • 修改 Item 點擊事件:
      	onPress={() => this.clickItem(selectedTab, subscription)}>
    
    • clickItem 方法邏輯:
      	// 點擊了Item
          clickItem(selectedTab, subscription) {
    
              if (subscription !== "" && this.state.selectedTab == selectedTab) {
                  // 發送通知
                  DeviceEventEmitter.emit(subscription);
              }
    
              // 渲染頁面
              this.setState({ selectedTab: selectedTab })
          }
    
    • 所以傳值也需要新增 subscription 參數,不需要訂閱的按鈕就可以傳 "" 即可。

    • 海淘也是類似操作,這邊就不贅述,自己試着實現一下。

每次點擊 Item 獲取到最新數據后我們需要及時更新 Item 角標


  • 實現思路很簡單,我們使用逆傳的方式,每次獲取到最新數據的時候,同時需要調用一下 在 main獲取最新數據個數 的請求方法即可。

    • 首先我們在 home 定義一個屬性供外界使用:

        	static defaultProps = {
                loadDataNumber:{},   // 回調
            };
      
    • 接着,當我們請求到最新數據的同時,我們調用一下這個屬性,就可以了:

        	// 獲取最新數據個數
        this.loadDataNumber();
      
        // loadDataNumber 中的邏輯
            loadDataNumber() {
        		// 調用 this.props.loadDataNumber 中保存的代碼塊
                this.props.loadDataNumber();
            }
      
    • 為了方便調用,我們將 獲取最新數據個數 的邏輯抽出來放到單獨的方法內,這邊順便再介紹 AsyncStorage 怎么同時獲取多個 key 值的方法:

        	// 獲取最新數據個數網絡請求
            loadDataNumber() {
                // 取出id
                AsyncStorage.multiGet(['cnfirstID', 'usfirstID'], (error, stores) => {
                    // 拼接參數
                    let params = {
                        "cnmaxid" : stores[0][1],
                        "usmaxid" : stores[1][1],
                    };
      
                    // 請求數據
                    HTTPBase.get('http://guangdiu.com/api/getnewitemcount.php', params)
                        .then((responseData) => {
                            this.setState({
                                cnbadgeText:responseData.cn,
                                usbadgeText:responseData.us
                            })
                        })
                        .catch((error) => {
      
                        })
                });
            }
      
    • 很好,接着我們轉到 main 中,修改下 renderTabBarItem 方法中的內容,實現一下 `` 屬性的方法:

        	renderScene={(route, navigator) => {
              let Component = route.component;
              return <Component {...route.params}
                                navigator={navigator}
                                loadDataNumber={() => this.loadDataNumber()} />
          }}
      
    • 到這里就完成了,海淘頁面也是類似操作,自己試着實現一下。

一鍵置頂功能


  • 一鍵置頂功能也是市面上 APP 上可以說必備功能了,這邊 原版APP 也有這個功能,所以我們跟着來實現一下。

  • 這個功能實現更加簡單,只要我們調用 ScrollViewscrollTo 方法,將 y 設置為 0 即可;因為 ListView 是在 ScrollView 上進行的二次開發,所以它可以使用 ScrollView 的所有方法:

      	// 點擊了Item
          clickTabBarItem() {
              let PullList = this.refs.pullList;
      		// 一鍵置頂
            PullList.scrollTo({y:0});
         }
    
  • 這樣我們的一鍵置頂功能就完成了,在需要的頁面進行同樣操作就可以實現相同功能。

TabBarItem 邏輯完善


  • 那么為了更好的用戶體驗,我們這邊還需要來處理一下點擊 TabBarItem 的一下細節,那就是當用戶點擊 Item 時,可能只是單純的想進行頁面的 切換或者置頂操作 ,而不想進行 刷新,那么我們就需要來判斷一下什么時候需要刷新,什么時候需要置頂。

  • 那么我們可以通過判斷 ListView 中的 Scroll 的偏移量來判斷是否需要進行置頂操作,當偏移量大於 1 的時候我們就進行置頂操作,否則的話我們就進行刷新操作。

  • 那么問題又來了,當我們執行刷新操作的時候,應該模擬用戶下拉顯示 滾動小菊花 來告訴用戶我們在進行刷新操作,可是 pullList 並沒有提供我們這個方法怎么辦?那我們就需要分析 第三方框架的內容 來找方法解決這個問題(具體方法,可以觀看我為各位錄制的視頻),這邊就不多講了,直接上最終代碼:

      	// 點擊了Item
          clickTabBarItem() {
    
              let PullList = this.refs.pullList;
    
              if (PullList.scroll.scrollProperties.offset > 0) {      // 不在頂部
                  // 一鍵置頂
                  PullList.scrollTo({y:0});
              }else {     // 在頂部
    
                  // 執行下拉刷新動畫
                  PullList.state.pullPan = new Animated.ValueXY({x: 0, y: this.topIndicatorHeight * -1});
    
                  // 加載最新數據
                  this.loadData();
    
                  // 關閉動畫
                  setTimeout(() => {
                      PullList.resetDefaultXYHandler();
                  },1000);
              }
          }
    

點擊Item最終效果.gif

關閉篩選菜單滑動手勢


  • 那這邊我們的篩選菜單還有個問題,就是可以響應我們的手勢進行滾動,這樣肯定是不對的,那么我們需要關閉這個手勢的監聽,使這個菜單不能滾動,具體操作如下:

      	{/* 菜單內容 */}
        <ListView
            scrollEnabled={false}								// 關閉滑動功能
            dataSource={this.state.dataSource}                  // 設置數據源
            renderRow={this.renderRow.bind(this)}               // 根據數據初始化 Cell
            contentContainerStyle={styles.contentViewStyle}     // 樣式
            initialListSize={16}                                // 一次性渲染幾行數據
        />
    

關閉篩選菜單滑動.gif


  • 到現在肯定有很多朋友發現 Navigator 跳轉動畫並不是那么流暢,會出現掉幀卡頓的現象,並不像 NavigatorIOS 那么絲絲順滑;造成這個的原因是因為 NavigatorIOS 是在 UI線程 執行的 動畫操作,而 Navigator 是在 JS線程執行的動畫,那這樣就會 阻塞住 JS線程,那么怎么去解決這個問題?這邊提供 2 種方案:

    • 第一種:使用 navigation 框架,這個是目前替代 navigator 最好的方案之一,很強大,很流暢,但是需要再去學習一下使用。

    • 第二種:如果你懶得學習上面的框架,那么這邊再給各位提供另一種方法 —— 使用官方提供的 API:InteractionManager(可以將一些耗時較長的工作安排到所有互動或動畫完成之后再進行。這樣可以保證JavaScript動畫的流暢運行),這邊我們就使用這種方案來進行一下優化:

        	InteractionManager.runAfterInteractions(() => {
              this.props.navigator.push({
                  component: Search,
              });
          });
      
  • 是的,就這一步操作即可,在其他需要用到 跳轉功能的地方 使用一下這個API即可。

Navigator掉幀處理.gif

怎樣調用框架中沒有提供我們使用的接口


  • 這個篇幅較大,感興趣的朋友還是參考錄制的視頻吧。

removeClippedSubviews


  • 用於提升大列表的滾動性能。需要給行容器添加樣式overflow:'hidden'。(Android已默認添加此樣式)此屬性默認開啟

  • 這個屬性是因為在早期 ListView 在數據到達一定程度的時候就會越來越卡,最終導致 APP 崩潰退出,使用這個屬性后 APP 崩潰確實在一定程度上得到緩解,但是卡頓問題還是依舊存在。那等到后面我們會介紹 FlatList ,它將是未來 ListView 替代品,主要解決它性能差的,占用內容持續增加的問題,目前還沒發布穩定版本,但是經過一段時間測試,我覺得已經可以向大家推薦了,所以在后面的章節中會為各位介紹的。

  • 廢了這么多話,這邊我們就先來使用一下 removeClippedSubviews ,很簡單,使用它只需要在我們封裝的 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 然后又關閉 modal 的時候,就會出現 ListView 列表消失的問題,那么其實是因為我們 modal 放置的順序問題,modal 應當放置到所有主視圖之后創建,避免它影響其他視圖顯示,這邊就以 home 頁面為例,其他視圖自己實現哈:

      	render() {
              return (
                  <View style={styles.container}>
                      {/* 導航欄樣式 */}
                      <CommunalNavBar
                          leftItem = {() => this.renderLeftItem()}
                          titleItem = {() => this.renderTitleItem()}
                          rightItem = {() => this.renderRightItem()}
                      />
    
                      {/* 根據網絡狀態決定是否渲染 listview */}
                      {this.renderListView()}
    
                      {/* 初始化近半小時熱門 */}
                      <Modal pointerEvents={'box-none'}
                             animationType='slide'
                             transparent={false}
                             visible={this.state.isHalfHourHotModal}
                             onRequestClose={() => this.onRequestClose()} >
    
                          {/* 包裝導航功能 */}
                          <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>
              );
          }
    
  • 原因我們之后會帶大家來自己 開發一個類似 modal 的組件,到時候再跟大家詳解。

安卓modal問題解決.gif

Android 加載git圖\動圖


  • 細心的朋友應該發現了一個問題,同樣一張 git 圖片,在 iOS 上可以正常加載,在 Android 上圖片竟然不能動,可能會想算了吧,能顯示圖片就行了?轉頭發現,產品經理正 “悠閑” 磨刀呢。。。那其實解決這個問題很簡單,我們只需要使用一下 facebokk 的一個強大的圖片加載庫就能解決這個問題了。

  • 首先,我們打開 build.gradle ,在 dependencies 中添加下面一行代碼

      	compile "com.facebook.fresco:animated-gif:0.13.0"
    
  • 重新 run 一下,編譯器會自動幫我們添加這個庫並配置完畢,那么 Android 上也可以愉快地顯示 gif圖片 了。

  • 什么?找不到這個文件?那可不行,看一下給各位錄制的視頻吧。

安卓動圖解決方案.gif

導航欄返回按鈕


  • 我們導航欄的返回按鈕很挫對吧,這邊統一簡單先改一下:

      	// 返回左邊按鈕
          renderLeftItem() {
              return(
                  <TouchableOpacity
                      onPress={() => {this.pop()}}
                  >
                      <View style={{flexDirection:'row', alignItems:'center'}}>
                          <Image source={{uri:'back'}} style={styles.navbarLeftItemStyle} />
                          <Text>返回</Text>
                      </View>
    
                  </TouchableOpacity>
              );
          }
    

導航欄返回按鈕樣式.gif

去除 Android 中輸入框的下划線


  • 那么 Android 中的 TextInput 的下划線是不是丑爆了?這邊我們也來處理下它,直接使用 underlineColorAndroid 這個屬性,讓他為透明即可。

      	underlineColorAndroid={'transparent'}
    

去除安卓輸入框下划線.gif


  • 這邊先來介紹一下 navigationBa 的使用,使用它可以讓我們只在一個地方管理 navigator 導航欄的樣式,就不用像現在這樣在每個頁面都手動添加導航欄。

  • 這邊先舉個例子讓大家知道怎么用,考慮到傳值不方便的原因,到講完 redux 之后,再一起講這部分內容,所以大家只需要了解一下怎么使用就可以了。

  • 首先,我們來看下 navigationBar 文件內的內容:

      	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文件中 使用一下這個 navigationBar

      	navigationBar={NavigationBar}
    

navigationBar.gif

react-native 開發中你可能需要的一些小玩意


  • 撥打電話(真機測試,模擬器沒有打電話功能):

      	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);
      	}
    
    
  • 開發中建議先從 iOS 端做起,安卓端適配;當然如果公司不是只有你一個人負責 react-native 項目,大可不必理會這條。

  • 開發中有些功能在 模擬器 上是無法測試的,這時候需要配合真機進行調試,下面整理出一些常見的問題:

    • 當 Android 提示找不到服務器時:

      • 確定電腦與我們手機連接同一個 WiFi 網絡環境下。

      • 我們需要打開 開發中菜單(搖下手機) —— —— —— 輸入電腦IP地址(IP地址怎么找?自己搜索吧)—— 退出菜單 ——

    • 當 iOS 提示找不到服務器時:

      • 打開 Xcode —— AppDelegate.m 文件 —— 更改 jsCodeLocation 中的 localhost 為電腦的IP地址 —— 重新運行一遍。

優化工具介紹與使用


  • 本來要講的,但是發現現在的項目還沒出現太明顯性能問題,不好測試,就放到最后面講吧。

第一版完結


  • 到這里第一個版本就完結了,接下來就要開始我們的第二版本的開發了,那么第二個版本之前大概會用一篇的內容來主要講下 react 怎么結合 redux 進行開發,並做個小 Demo,讓大家先熟悉一下 redux 使用,然后就是我們當前的項目 轉為 redux 開發了。

  • 這邊想知道大家更想知道或者對於 redux 哪里比較不理解的,好跟着改進一下,盡量使文章和視頻更易懂。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM