React Native 在「元氣閱讀」的實踐

閱文前端團隊發表於2018-08-01

本文作者:閱文集團前端團隊

原創宣告:本文為閱文前端團隊 YFE 成員出品,請尊重原創,轉載請聯絡公眾號 (id: yuewen_YFE) 獲取授權,並註明作者、出處和連結。

前言

經歷了三個多月的集中開發,閱文集團旗下二次元產品「元氣閱讀」APP 終於在各大應用商店上架了。「元氣閱讀」APP 大部分的功能模組基於 React Native 開發,整個開發過程前端團隊趟了不少 React Native 的坑,同時也積累了不少實踐心得,與大家一起分享。

一、業務背景與技術選型

在使用 React Native (以下簡稱RN)之前,和業界大部分團隊一樣, 我們 APP 的開發模式採用的是客戶端(iOS/Android)內嵌 H5 的 Hybrid 開發模式。一開始,我們除了採用比較成熟的離線包方案管理靜態資源,在首屏載入體驗上我們也做了不少優化工作,但發現 H5 線上的體驗和效能資料與原生還是有不少差距,所以我們決定引入新方案。

RN 和 Weex 已經是業界兩個相對成熟的 Hybrid 解決方案,基本能滿足我們的需求:

  • 使用者體驗:相比於 H5 頁面,RN 和 Weex 在使用者體驗上有了很大的提升,體驗幾乎接近原生
  • 人力成本:相比於客戶端,RN 和 Weex 的一套程式碼可以跑在 iOS、Android 兩端,且程式碼重用率也較高
  • 靈活釋出:RN 和 Weex 都擁有熱更新能力

最終我們選擇了 RN 作為解決方案,主要是考慮了幾個因素:

  • 社群現狀:相對 Weex,RN 的社群活躍度和 Facebook React 周邊生態更好
  • 大廠背書:騰訊、京東、百度和攜程都有大型產品線上上跑
  • 團隊現狀:早在 17 年上半年,閱文前端團隊已經選型 React 作為我們前臺產品線的主要研發技術棧,且大部分成員都能駕馭 React

二、應用場景

在「元氣閱讀」APP 中,使用 RN 開發的應用場景達到了 70% 左右。使用者能看到的頁面中,除了書架、註冊登入和閱讀引擎,其它模組幾乎都是使用 RN 完成開發,「元氣閱讀」APP 已經屬於國內大型產品中,超大規模的 RN 應用了。歡迎大家在各應用商店(iOSAndroid)搜尋「元氣閱讀」下載體驗。

▲小說書城

▲小說書城

▲漫畫書城

▲漫畫書城

▲元氣圈

▲元氣圈

▲漫畫詳情

▲漫畫詳情

▲排行榜

▲排行榜

▲分類

▲分類

三、導航管理

對於 RN 的開發,導航的前期規劃十分重要,通常在搭建專案時就需要提前考慮。關於導航元件的選擇,react-navigation 是個不錯的選擇,我們希望 react-navigation 能在業務場景更加通用。

1、統一跳轉規則

Native 與 RN 互跳是最常見的需求。有了統一的 URL,只需維護一份 sitemap 和實現一個 open 介面,就可以很容易的在 Native 與 RN 中互相跳轉。

react-navigation 是使用 routeName + params 的形式跳轉的,所以需要在呼叫 router.getStateForAction 之前做一點調整:

// 修正 action: 允許 navigate/push/reset 動作傳 url
if (isPushLikeAction(action) || isReplaceAction(action)) {
  if (isRouteUrl(action.routeName)) {
    // 使用 path-to-regexp 庫來判斷 url 對應的 routeName + params
    const route = parseRouteByUrl(action.routeName) 
    if (route) {
      action.routeName = route.name
      action.params = route.params
    }
  }
}
複製程式碼

2、實現 404 跳轉

在 Web 開發中,404 頁面是一個很常見的邏輯,參照上面的方式, RN 可以這樣實現:

// 修正 action: 當 navigate/push/replace 跳轉到未知 routeName 時,調整為定義的 notFoundRouteName
if (isPushLikeAction(action) || isReplaceAction(action)) {
  // 修正 action: 提供 404 能力
  if (allRouteNames.indexOf(action.routeName) === -1) {
    const oldAction = { ...action }
    action.routeName = notFoundRouteName
    action.params = { action: oldAction }
  }
}
複製程式碼

3、控制頁面生命週期

在專案開發過程中,經常碰到這樣的需求,回到原來頁面之後要重新整理原頁面的資料,比如登入之後、進入詳情頁完成某操作之後回到列表頁等。

「元氣閱讀」專案剛啟動時 react-navigation 還是 0.x 版本,只能用 onNavigationStateChange + context 才能讓頁面感知 focus/blur 。1.x 版本之後,我們可以通過自帶的 addListener 方法來監聽 didFocus 或 didBlur 事件。

4、優化頁面二次開啟

「元氣閱讀」是一個以 RN 為入口的應用,在正常的使用過程中,需要頻繁的從 RN 切換到 Native 或從 Native 切換到 RN,這樣就會有多個 RN 頁面(根元件),而第二個根元件在初始化的時候就需要定位到指定頁面,所以和 Native 約定,通過 initialRouteUrlinitialRouteName + initialRouteParams 來告訴 RN 需要定位到什麼頁面:

const navigator = getActiveNavigator() // 需要全域性維護一個 Navigator 的堆疊
let nextState = originGetStateForAction(action, state) // 呼叫原始的 getStateForAction 獲取新的/初始化的狀態

if (navigator) {
  const { initialRouteName, initialRouteParams, goBackOnTop } = navigator.props // 讀取 navigator 的 props

  if (isInitAction(action)) {
    // 支援通過 initialRouteName & initialRouteParams 初始化到相應頁面
    if (initialRouteName) {
      const initialActionPayload = { routeName: initialRouteName, params: initialRouteParams }
      const initialAction = NavigationActions.navigate(initialActionPayload)
      nextState = router.getStateForAction(initialAction, nextState) 
       if (!isTopNavigator() && nextState.index > 0) {
        // 非第一層 RN 例項且有兩個頁面的時候(前面 navigate 到了非一級頁面),保留最後一個頁面
        nextState = {
          ...nextState,
          index: 0,
          routes: nextState.routes.slice(-1),
        }
      }
    }
  } else if (isBackAction(action)) {
    // 在第一層頁面,並且不是是第一個 Navigator,則呼叫 goBackOnTop 關閉 RN 
    if (isTopScren(state) && !isTopNavigator( ) && typeof goBackOnTop === 'function') { 
      goBackOnTop()
      if (nextState === state) {
        // 防止 Android 的物理返回鍵導致退出 App
        nextState = { ...nextState }
      }
    }
  }
}

return nextState
複製程式碼

5、狀態本地儲存

元件 react-navigation 在 2.x 版本新增了狀態本地儲存功能,在 reload 之後可以直接定位到之前的頁面,但是需要注意兩個點:

  • 在「元氣閱讀」這種多個根元件的業務場景,每一個根元件的 rootNavigator 需要有個標記區分,建議以索引區分
  • 在頁面出錯(紅屏)之後,為了避免 reload 還是停在當前錯誤頁,可以在 componentDidCatch 裡面清除本地儲存

四、狀態管理與資料持久化

在「元氣閱讀」裡,我們經常需要快取使用者的資訊、瀏覽過的書詳情資訊以及使用者收到的訊息等等,這樣使用者在離線訪問「元氣閱讀」時就能避免白屏或異常的情況,而且還可以實現“秒開”。

舉個例子,當使用者第一次開啟書籍詳情頁的時候,把書書籍詳情的資訊快取下來;第二次再開啟的時候,就可以達到秒開的效果。秒開效果可以看下圖:

▲跳轉詳情頁

▲跳轉詳情頁

我們選擇 reduxredux-persist 搭配一起使用,來實現資料共享以及資料持久化快取。

1、redux

選擇用 redux 主要是實現資料共享的功能。通過 redux 單項資料流的特點,每一步操作都有跡可循,比較容易排查問題。

在寫 redux 的時候,可能大家覺得會需要寫很多樣板程式碼。在這裡推薦一下 redux-actions 這個庫,能夠幫助我們減少一些程式碼量。下面簡單的舉一下例子:

// 常見的寫法
export default (state = {}, action) => {
 switch (action.type) {
  case INCREASE:
   return {...state, total: state.total + 1}
  case DECREASE:
   return {...state, total: state.total - 1}
  default:
   return state
 }
}

// 通過 handleActions 方法
import { handleActions } from 'redux-actions'

export default handleActions({
 [INCREASE]: state => {
  ...state,
  total: state.total + 1
 },
 [DECREASE]: state => {
  ...state,
  total: state.total - 1
 }
}, initialState = {})
複製程式碼

2、redux-persist

redux-persist 會訂閱 store,一旦 store 發生變化,就會觸發儲存操作。這樣當我們操作 store 的時候,資料也就會更新到本地了。

在開發專案的時候可能會發現,我們在 store 中共享的資料有一些可能是不需要被快取到本地的。比如說搜尋結果頁,因為每次搜尋的關鍵字不一樣,結果也是不一樣的,這樣的資料被快取到本地就沒有意義。那我們怎麼來控制一些資料不被快取到本地呢?

redux-persist 支援配置黑白名單,意思是只持久化白名單中的資料或者不持久化黑名單中的資料。這樣就可以根據需求來配置黑白名單,從而決定哪些資料需要被快取到本地,哪些資料不需要被快取。例如:

import { createStore, applyMiddleware, combineReducers } from 'redux'
import { persistReducer } from 'redux-persist'
import thunkMiddleware from 'redux-thunk'
import storage from 'redux-persist/lib/storage'

const rootPersistConfig = {
 storage,
 key: '***',
 blacklist: ['***'] // 黑名單
}

const enhancer = applyMiddleware(thunkMiddleware)
export const store = createStore(persistReducer(rootPersistConfig, rootReducer), enhancer)
export const persistor = persistStore(store)
複製程式碼

五、效能優化

A compelling reason for using React Native instead of WebView-based tools is to achieve 60 frames per second and a native look and feel to your apps. Where possible, we would like for React Native to do the right thing and help you to focus on your app instead of performance optimization, but there are areas where we're not quite there yet, and others where React Native (similar to writing native code directly) cannot possibly determine the best way to optimize for you and so manual intervention will be necessary. We try our best to deliver buttery-smooth UI performance by default, but sometimes that just isn't possible.

在 RN 文件裡看到一段關於效能的解讀,裡面提到:「目前在某些場合 RN 還不能夠替你決定如何進行優化(用原生程式碼寫也無法避免),因此人工的干預依然是必要的」,我們確實在效能優化上花費了不少精力。

1、首屏優化

執行過 RN 專案的同學不難發現,我們第一次進入 RN 頁面時會有一個短暫的白屏,快至幾十毫秒,慢至 1 到 2 秒,白屏時間取決於終端的效能,在低端安卓機子表現最差,而且退出後再進入,仍然會有這個白屏。我們實施了幾個優化策略:

1)預載入 Bundle

在客戶端啟動時,就開始對 RN 的 bundle 進行預先載入,我們發現這樣操作後,白屏操作的時間縮短了不少,特別是安卓裝置。但這還不是最完美的,我們仍然會看到很短暫的白屏。

2)優化閃屏邏輯

由於大部分 APP 一定是先有閃屏,然後才進入首頁。我們完全可以利用這個業務場景,讓 RN 程式躲在閃屏下載入,直到載入完畢,通過 Bridge 通知客戶端把閃屏關閉,這樣就比較巧妙地解決了白屏的問題。

▲before
▲before

▲after
▲after

2、互動優先

當 JavaScript 執行緒中同時做很多事情時,很容易就會導致執行緒掉幀,表現為頁面卡頓、動畫切換緩慢,我們可以使用“互動優先”的原則去做優化。

1)優先執行使用者可感知的操作:如頁面場景切換

例如,頁面轉場這個場景。我們就可以把頁面邏輯放在 InteractionManager.runAfterInteractions 的回撥中執行,這樣可以優先保證轉場動畫的執行,然後才是我們的頁面邏輯,很好的規避了轉場卡頓的問題。

2)初始化頁面儘量渲染少量元件

當我們呈現一個頁面給使用者時,一定是要在最短時間內讓使用者感覺到頁面已經展現完畢了,所以我們在初次展示頁面時,可以優先顯示固定的佔位資訊,配合 loading 或骨架圖佈局不確定的部分,與此同時我們才在背後默默的發起請求(碰到複雜頁面,則可拆分多個非同步請求),總之整個過程是先保證頁面可見,再逐步完整。

3、長列表優化

▲元件的子樹
▲元件的子樹

這是一個元件的子樹。對其中每個元件來說,SCU 表明了 shouldComponentUpdate 的返回內容,vDOMEq 表明了待渲染的 React 元素與原始元素是否相等,最後,圓圈的顏色表明這個元件是否需要重新渲染。

在 React 中如果只是一次這樣的元件子樹渲染,並不會有太大的效能問題。但如果對於分頁長列表這種需要成百上千次的渲染場景,會花費很大的開銷在 vDOM 的生成和 Diff 上,而這也直接導致了長列表在 RN 中嚴重的效能問題。那我們需要做些什麼加以改進呢?先來看看這張元件更新渲染的流程圖:

▲元件更新流程
▲元件更新流程

當一個元件的 state 或者 props 改變時,就進入了生命週期函式 shouldComponentUpdate,而當 shouldComponentUpdate 返回的是 true ,就會呼叫 render 方法生成 Virtual Dom,隨後和舊的 Virtual Dom 進行比對,最終決定是否更新。所以從中我們明顯地看出 SCU 和 Virtual Dom 的 Diff 是影響 Dom 更新的關鍵所在,為此我們分別針對這兩點做了優化:

1)控制好 shouldComponentUpdate 的更新邏輯

從上圖也可以看出如果 shouldComponentUpdate 返回的是 false,那程式就可以直接跳過生成 Virtual Dom 以及之後的 Diff,這對於一個大列表的場景是相當可觀的優化,例如目前我們有一個 1000 條資料的列表,在下拉載入 20 條新資料時,如果沒有利用 shouldComponentUpdate 進行控制,會把之前的 1000 條資料也 render 一遍,而在 shouldComponentUpdate 中控制好更新邏輯,就只需要 render 最新的那20條,是不是很大的提升!不過使用 shouldComponentUpdate 要格外小心,你一定要考慮到所有影響更新的邏輯。不然會出現真正需要更新的時候卻也沒能更新。

來看一個具體的例子,場景是 APP 中的分類列表頁,我們在每一個列表項的 render 中列印 log,統計進入 render 的次數。首先來看看 shouldComponentUpdate 不做任何處理的情況,也就是 shouldComponentUpdate 始終返回的是 true:

shouldComponentUpdate (nextProps, nextState) {
  return true
}
複製程式碼

▲before
▲before

再看看我們在 shouldComponentUpdate 中以圖片的 uri 地址過濾掉不必要的渲染項之後的情況:

shouldComponentUpdate (nextProps, nextState) {
  if (nextProps.imgSrc.uri === this.props.imgSrc.uri) {
    return false
  } else {
    return true
  }
}
複製程式碼

▲after
▲after

從圖中左邊的控制檯很明顯的看出,過濾後不論載入到哪一頁,都只是渲染最新的20條,減少了大量不必要的渲染。再比較一下在相同條件下兩者載入一千條資料的時間:

React Native 在「元氣閱讀」的實踐
結果也是顯而易見,而且在操作過程中發現未使用 shouldComponentUpdate 的情況下,越往後會越慢,到 1000 條資料時,再載入新資料所要等待的時間簡直無法忍受。

2)在陣列遍歷時,增加唯一標識的 key 值

如果更新是不可避免的,那隻能想辦法去提高 Virtual Dom 的 Diff 效率。我們可以在遍歷陣列時給每一項加上唯一的 key 值,這樣在 Diff 階段,可以準確知道要操作的子元件,提高 Diff 的效率。

4、動畫優化

合理運用動畫對於 APP 的體驗提升有很大幫助。但我們在應用動畫時發現在有些場景會出現卡頓、掉幀的現象,本質原因是由於 JavaScript 是單執行緒的,如果執行緒中在跑一些比較重的任務,就可能會對動畫的效能出現影響。下面介紹幾種辦法,把動畫這件事儘量交於原生:

1)使用 LayoutAnimation

針對一次性動畫,建議使用 LayoutAnimation,它利用了原生的 Core Animation,使動畫不會被 JS 執行緒和主執行緒的掉幀所影響。

2)使用 setNativeProps

setNativeProps 方法可以使我們直接修改基於原生檢視元件的屬性,而不需要使用 setState 來重新渲染整個元件樹。避免了渲染元件結構和同步太多檢視變化所帶來的大量開銷。

3)使用原生驅動的方式

在 Animated 動畫設定中,新增 useNativeDriver 欄位,並設為 true,這樣就可以把動畫的執行交由原生處理。

六、釋出更新

如今由於網際網路高速傳播的特效,事物發展的速度越來越快,產品快速迭代、試錯的能力就顯得尤為關鍵,作為開發者,對我們的挑戰就是如何讓開發完成的功能快速上線,下面來看看我們是怎麼做的:

1、釋出

我們選擇 Jenkins 作為自動化部署方案。通過配置在 Jenkins 中打包指令碼來實現自動打包,把 RN 的 bundle 包打到指定的位置,這樣就不用每次打包之前再手動打包了,大大提高了效率。

2、熱更新

由於 Native 端釋出一次新版本的成本比較大,RN 的熱更新能力就成為了很大的亮點。只需要把最新的 bundle 包釋出到伺服器,就能夠讓使用者手中的 app 自動下載遠端的 bundle 包,然後無感知的更新,可謂是特別的方便。

我們經過調研,最終選擇了微軟的 CodePush。它提供給 RN 和 Cordova 開發者直接部署移動應用更新給使用者裝置的雲服務,而且還開源了 RN 版本。具體接入的教程可以檢視官方網站,這裡就不一一贅述了。下面主要講幾個需要注意的點:

1)註冊 app

在 CodePush 上註冊 app 的時候,需要區分 iOS 和 Android,例如 appName-iosappName-android,在釋出的時候需要在不同的平臺分開發布。

2)key 的配置:Staging 和 Production

在註冊 app 的時候,會返回一套 deployment key,分別為 Production 和 Staging 環境(後續也可以自定義 deployment key 名稱),在整合 CodePush SDK 的時候會用到。Production 對應生產環境的 key,Staging 對應測試環境的 key。這樣就可以分別更新不同環境的包。如果想要檢視 app 的 deployment key 表,可以使用下面的命令:

code-push deployment ls <appName> -k
複製程式碼

React Native 在「元氣閱讀」的實踐

3)RN 接入 CodePush

RN 端接入 CodePush 非常簡單,只需要在根檔案中加入幾行程式碼就可以了。CodePush 傳參的時候可以根據環境的不同做不同的配置。程式碼大致為下面這樣:

import React, { Component } from 'react'
import codePush from 'react-native-code-push' // 引入 codePush
       
  const codePushOptions = __DEV__ ? {
    updateDialog: true, // 顯示更新彈窗
    installMode: codePush.InstallMode.IMMEDIATE // 立即更新(會打斷使用者操作)
  } : {
    // 下次 app 從後臺切換到前臺時檢查更新,並下載最新的包
    checkFrequency: codePush.CheckFrequency.ON_APP_RESUME,
    // 下次重啟的時候更替換成最新的包
    installMode: codePush.InstallMode.ON_NEXT_RESTART
  }
       
  @codePush(codePushOptions)
  export default class App extends Component {
    render() {
      ...
    }
  }
複製程式碼

checkFrequencyinstallMode 是可配置的,具體的配置可以根據需求來決定。

4)版本控制

在熱更新的時候需要控制版本號,預設是當前安裝包的版本(三位數版本號),如果需要指定版本號的話,可以在執行熱更新命令的時候加上 -t,後面跟需要更新的版本號就行了。

七、異常監控

我們藉助了騰訊 Bugly 平臺進行線上異常的監控。Bugly 平臺能為開發者提供異常上報與運營統計功能:

  • Bugly 會上報執行錯誤、崩潰和卡頓的異常,並提供相應的資料統計和告警機制,使我們可以儘可能快地感知到線上異常,掌握使用者側整體的運營穩定性和流暢度的情況;
  • Bugly 平臺提供日誌上報功能,可以協助定位問題;
  • Bugly 平臺也可以針對版本,機型,系統,來對比異常資料的變化。

例如,下圖是對 Crash 率的統計:

React Native 在「元氣閱讀」的實踐

Crash 還可以根據系統、裝置和 APP 版本等維度來細化分析。

React Native 在「元氣閱讀」的實踐

還可以統計最影響使用者的 Top 問題:

React Native 在「元氣閱讀」的實踐

八、一些坑和小貼士

在幾個月的開發過程中,我們遇到了不少坑,也發現了一些好用或者沒有被注意到的小技巧,下面和大家分享其中的一部分:

1、坑

1)Image 元件在 Android 上潛在的記憶體洩漏 Bug

在安卓中,載入一張尺寸遠大於容器的圖片,記憶體會突然猛漲,在這張圖上下滑動,程式就直接因為記憶體不足而崩潰瞭如何解決呢?其實辦法也很簡單,只需要設定 Image 元件的 resizeMethod 屬性為 resize 即可,如下圖:

▲Image的resizeMethod屬性說明
▲Image 的 resizeMethod 屬性說明

2)使用 InteractionManager.runAfterInteractions 時的注意事項

我們知道 InteractionManager.runAfterInteractions 的回撥是需要完成動畫後才執行,我們的程式中發現過這樣一個的 bug,在點選某個按鈕後,就怎麼也進不到 runAfterInteractions 的回撥中。經過排查,原來是我們執行了一個無限迴圈的動畫(loading 效果),並且沒有關閉,所以就永遠進不到 runAfterInteractions 的回撥了。所以大家在開發中碰到迴圈動畫要注意處理。

3)使用 FlatList 列表出現頁面跳動問題

FlatList 有一個叫 getItemLayout 的優化屬性,如果你是個定高的列表項,設定這個屬性可以大大提高列表渲染的效率。然後我們遇到的問題是,在高度不確實的時候,也設定了這個屬性,導致最終渲染時實際高度和我們預設的值不一致,出現了跳動。所以,如果不確定高度,千萬別設定 getItemLayout 屬性。

▲滑動不順暢,會發生跳動
▲滑動不順暢,會發生跳動

▲正常滑動
▲正常滑動

4)短時間內重複點選出現多個相同頁面的問題

這不單單是 RN 的問題,各端應該都無法避免。所以通常在各種技術棧的導航庫中都對此進行了修復,我們剛開始的預期就是 React Navigation 在內部肯定解決了這個問題,但發現實際上並沒有。於是我們就對 React Navigation 的跳轉做了一次增強,思路是判斷下個路由的地址和上個路由一致,那就不予處理:

▲重複跳轉
▲重複跳轉

▲解決後
▲解決後

function isInCurrentState (state, nextState, routeName) {
  if(nextState && nextState.routeName === routeName && !deepDiffer(state.params, nextState.params)) {
    return true
  }
  if(nextState && nextState.routes) {
    return isInCurrentState(state.routes[state.index], nextState.routes[nextState.index], routeName)
  }
  return false
}

const nextState = originGetStateForAction(action, state)

// 避免重複跳轉
if (nextState && action.type === StackActions.PUSH) {
  if(isInCurrentState(state, nextState, action.routeName)) {
    return state
  }
}
複製程式碼

2、小貼士

1) iOS 模擬器中你可能不知道的兩個選項

  • 開啟虛擬鍵盤:我們在開發輸入相關場景時,iOS模擬器預設未開啟鍵盤,需要在 HardWare->Keyboard->Toggle Software Keyboard 進行開關;
  • 慢動畫開關:很多同學碰到這個問題,不知道點了哪個按鍵後,模擬器中的任何操作都得比無比緩慢,各種重啟、清快取都無效。這是由於我們不小心觸發了快捷鍵,開啟了慢動畫模式,可以在 Debug->Slow Animations 中關閉(快捷鍵是 command+T)。

2)原來 RN 和原生的通訊也可以是同步的

我們知道 RN 和原生的通訊是非同步的,但如果是一些全域性的常量(環境變數、版本資訊等),其實可以以同步的方式在啟動 RN 時直接掛在 NativeModules 上,這樣使用起來就很方便。

3)Image 元件一些值得關注的屬性

  • defaultSource(iOS Only):正常我們要實現一個預設圖功能,需要先給圖片設定預設圖連結,然後在圖片下載成功的回撥裡再改變狀態,替換預設圖。這個屬性就幫你做好了這些,可惜的是隻支援 iOS。
  • getSize:當我們要獲取圖片的寬高,然後再處理圖片相關邏輯,就可以用這個 API。
  • prefetch:對圖片強制快取。
  • queryCache:這個 API 可以獲取到圖片是否快取,如果已快取,則下發是在硬碟還是記憶體。對於要處理一些快取邏輯還是很有用的,不過要注意的是雖然官方沒有標註 Android Only,我們只在 Android 獲取成功過,iOS 並沒成功。

4)Text 元件裡一些值得關注的屬性

  • allowFontScaling(iOS Only):這個屬性用來控制是否跟隨系統字型大小。如果你的APP佈局會因為設定字型而失控,可以考慮開啟,不過此屬性只支援 iOS,安卓需要其它方法解決。
  • selectable:這個屬性可以用來開啟文字的複製、貼上功能。

5)FlatList如何實現一行多列

FlatList 提供了一個叫 numColumns 的屬性,你只需要設定一行的列數,便可輕鬆實現一行多列的佈局如下圖:

▲一行三列的佈局

▲一行三列的佈局

6)除錯工具

推薦使用 react-native-debugger,它整合了 Chrome 的 DevTools 以及 react-devtools ,還支援 Redux 的相關除錯,可以說是很強大了。

7)效能檢測

可以通過客戶端自帶的軟體進行效能檢測。iOS 推薦 Xcode 自帶的 Profile;Android 推薦 Android Studio 自帶的 Android Profiler

九、總結

雖然 RN 目前還存在著一些不足,但通過「元氣閱讀」專案實踐,結果證明在人力、效能和效率上,RN 是符合我們預期的。對於 RN 在業務場景的最佳應用,我們也總結了幾點:

  • 重運營場景:有運營需求的場景,適合用 RN 實現,如書城頁、福利頁等
  • 快速迭代場景:功能未健全,產品需迭代試錯的功能場景,適合用 RN 實現,如元氣圈、小說書城、漫畫書城等
  • 固定資訊展示場景:固定內容的資訊展示頁面,適合用 RN 實現,如排行榜、本大人、一級分類頁等
  • 長列表場景
    • 由於 RN 列表的渲染機制限制,圖+文長列表裡有大量未知尺寸的圖片,不太建議 RN 實現,如類似元氣圈、微信朋友圈場景,暴力滑屏列表有機率出現閃白;
    • 大量已知尺寸的圖片長列表、純文字長列表,效能還可以接受

一個頁面用 Native 還是 RN 來實現,除了考慮各端團隊人員配比,業務場景也是一個重要的考慮因素。譬如新專案中,作品詳情頁用 Native 或 RN 實現都能達到驗收目標,但考慮到作品詳情頁產品場景已經很成熟,且有不少模組與核心閱讀頁有較多的互動,對體驗要求也特別高,我們與終端團隊一致選擇 Native 來實現。

寫在最後

近期 Airbnb、Udacity 團隊紛紛表示棄用 RN,筆者認為大家大可不必為此憂心忡忡。Airbnb 列舉的條例,其中不少項是可優化,或者結論是有待考究的;另外一些也有公司內部自身存在的問題。最近 Facebook 團隊宣佈正在努力打造一次大的升級,其中提到的對執行緒模型、非同步渲染和橋接的優化方向,也讓我們十分期待,我們有理由相信 RN 的未來會更好,也希望能通過這篇分享有更多的同學加入 RN 的大家庭,共同打造更好的 RN 生態。

更多分享,請關注YFE:

React Native 在「元氣閱讀」的實踐

相關文章