React-Native 之 項目實戰(五)

前言


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

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

  • 本章許多內容原本是要放到後面講的,考慮到有朋友可能不須要了解 redux 相關內容,因此把能想到的一些常見的東西先講下。android

小時風雲榜按鈕處理


  • 在服務器返回給咱們的 json 數據中,提供了 hasnexthour 字段,當這個字段返回爲 1 的時候,表示後面還有內容,按鈕能夠點擊,不然不能點擊,按照這個思路,咱們就來完成這個功能。git

  • state 中新增 isNextTouch 狀態github

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

    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,    // 更新按鈕狀態
        });
  • 接着咱們就能夠根據狀態進行相應更改:redux

    {/* 下一小時按鈕 */}
        <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 而使用,某些狀況下可能會拔苗助長。react-native

  • 使用方法以下:api

    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 而後又關閉 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 哪裏比較不理解的,好跟着改進一下,儘可能使文章和視頻更易懂。

相關文章
相關標籤/搜索