如何全方位優化你的超大型React應用 【原創精讀】

React爲了大型應用而生, ElectronReact-native賦予了它構建移動端跨平臺 App和桌面應用的能力, Taro則賦予了它一次編寫,生成多種平臺小程序和 React-native應用的能力,這裏特地說下  Taro,它是國產,文檔寫得比較不錯,並且它的升級速度比較快,有 issue我看也會及時解決,他們的維護人員仍是很是敬業的!
  • Tips:本文某些知識點若是介紹不對或者不全的地方歡迎指出,本文可能內容比較多,閱讀時間花費比較長,可是但願你能夠認真看下去,能夠的話最好手把手去實現一些code,本文全部代碼均手寫。

本文會從原生瀏覽器環境,到跨平臺開發逐漸去深刻介紹,先給一些資料

  • 手寫React優化腳手架帶項目
  • react-ssr的源碼
  • 手寫Node.js原生靜態資源服務器
  • 跨平臺Electron的demo

原生瀏覽器環境:

  • 原生瀏覽器環境實際上是最考驗前端工程師能力的編程環境,由於咱們前端大部分一開始面向瀏覽器編程,如今不少不少工做5-10年的前端,性能面板API都不知道用,怎麼看調用函數分析耗時都不知道,這也是最近面試的狀況,以爲有人說35歲失業的狀況,是廣泛存在,可是很大部分是你在混啊兄弟。
原生瀏覽器環境中使用React框架,比較常見的是製做單頁面SPA應用:
原生的SPA應用,分如下幾種:
  • CSR渲染(客戶端渲染)
  • SSR渲染(服務端渲染)
  • 混合渲染(預渲染,webpack的插件預渲染,Next.js的約定式路由SSR,或者使用Node.js作中間件,作部分SSR,加快首屏渲染,或者指定路由SSR.)
下面會分別仔細介紹這幾種渲染形式的精細化渲染,以及優缺點:

CSR渲染

  • 客戶端請求RestFul接口,接口吐回靜態資源文件
  • Node.js實現代碼
  • 客戶端收到一個HTML文件,和若干個CSS文件,以及多個javaScript文件
  • 用戶輸入了url地址欄而後客戶端返回靜態文件,客戶端開始解析
  • 客戶端解析文件,js代碼動態生成頁面。(這也是爲何說單頁面應用的SEO不友好的緣由,初始它只是一個空的div標籤的HTML文件)
  • 判斷一個頁面是否是CSR,很大程度上能夠根據右鍵點開查看頁面元素,若是隻有一個空的div標籤,那麼大機率能夠說是單頁面,CSR,客戶端渲染的網頁。

CSR的應用,如何精細化渲染呢?html

單頁面採起CSR形式,大都依賴框架,VueReact之類。一旦使用這類型技術架構,狀態數據集中管理,單向數據流,不可變數據,路由懶加載,按需加載組件,適當的緩存機制(PWA技術),細緻拆分組件,單一數據來源刷新組件,這些都是咱們能夠精細化的方向。每每純CSR的單頁面應用通常不會太複雜,因此這裏不引入PWAweb work等等,在後面複雜的跨平臺應用中我會將那些技術蜂擁而上。

  • 單一數據來源決定組件是否刷新是精細化最重要的方向。

一旦業務邏輯很是複雜的狀況下,假設咱們使用的是 dva集中狀態管理,同時鏈接這麼多的狀態樹模塊,那麼可能會形成狀態樹模塊中任意的數據刷新致使這個組件被刷新,可是其實這個組件此時是不須要刷新的。
  • 這裏能夠將須要的狀態經過根組件用props傳入,精確刷新的來源,單一可變數據來源追溯性強,也更方便debug
  • 單向數據流不可變數據,經過immutable.js這個庫實現

不可變數據,數據共享,持久化存儲,經過 is比較,每次 map生成的都是惟一的 ,它們比較的是 codehash的值,性能比經過遞歸或者直接比較強不少。在 PureComponent淺比較很差用的時候
  • 通常的組件,使用PureComponent減小重複渲染便可
  • PureComponent,平時咱們建立 React 組件通常是繼承於 Component,而 PureComponent 至關因而一個更純淨的 Component,對更新先後的數據進行了一次淺比較。只有在數據真正發生改變時,纔會對組件從新進行 render。所以能夠大大提升組件的性能。
  • PureComponent部分源碼,其實就是淺比較,只不過對一些特殊值進行了判斷

這裏特別注意,爲何使用immutable.js和pureComponent,由於React一旦根組件被刷新,會自上而下逐漸刷新整個子孫組件,這樣性能損耗重複渲染就會多出不少
因此咱們不只要單一數據來源控制組件刷新,偶爾還須要在shouldComponentUpdate中對比nextProps和this.props 以及this.state以及nextState
  • 路由懶加載+code-spliting,加快首屏渲染,也能夠減輕服務器壓力,由於不少人可能訪問你的網頁並不會看某些路由的內容
  • 使用react-loadable,支持SSR,很是推薦,官方的lazy不支持SSR,這是一個遺憾,這裏須要配合wepback4optimization配置,進行代碼分割
Tips:這裏須要下載支持動態 importbabel預設包 @babel/plugin-syntax-dynamic-import ,它支持動態倒入組件

  • 好了,如今路由懶加載組件以及代碼分割已經作好了,並且它支持SSR。很是棒
  • 因爲純CSR的網頁通常不是很複雜,這裏再介紹一個方面,那就是,能不用redux,dva等集中狀態管理的狀態就不上狀態樹,實踐證實,頻繁更新狀態樹對用戶體驗來講是影響很是大的。這個異步的過程,更耗時。遠不如支持經過props等方式進行組件間通訊,原則上除了不少組件共享的數據才上狀態樹,不然都採用其餘方式進行通訊。

SSR,服務端渲染:

服務端渲染能夠分爲:
純服務端渲染,如jade,tempalte,ejs等模板引擎進行渲染,而後返回給前端對應的HTML文件
  • 這裏也使用Node.js+express框架
混合渲染,使用webpack4插件,預渲染指定路由,被指定的路由爲SSR渲染,後臺0代碼實現

混合渲染,使用Node.js做爲中間件,SSR指定的路由加快首屏渲染,固然CSS也能夠服務端渲染,動態Title和meta標籤,更好的SEO優化,這裏Node.js還能夠同時處理數據,減輕前端的計算負擔。
  • 我以爲掘金上的神三元那篇文章就寫得很好,後面我本身去逐步實現了一次,感受對SSR對理解更爲透徹,加上原本就天天在寫Node.js,還會一點Next,Nuxt,服務端渲染,以爲大同小異。
  • 服務端渲染本質,在服務端把代碼運行一次,將數據提早請求回來,返回運行後的html文件,客戶端接到文件後,拉取js代碼,代碼注水,而後顯示,脫水,js接管頁面。
  • 同構直出代碼,能夠大大下降首屏渲染時間,通過實踐,根據不一樣的內容和配置能夠縮短40%-65%時間,可是服務端渲染會給服務器帶來壓力,因此折中根據狀況使用。
  • 如下是一個最簡單的服務端渲染,服務端直接吐拼接後的html結構字符串:
只要客戶端訪問 localhost:3000就能夠拿到數據頁面訪問
服務端渲染核心,保證代碼在服務端運行一次,將reduxstore狀態樹中的數據一塊兒返回給客戶端,客戶端脫水,渲染。保證它們的狀態數據和路由一致,就能夠說是成功了。必需要客戶端和服務端代碼和數據一致性,不然SSR就算失敗。

render函數:前端

  • 數據注水,脫水,保持客戶端和服務端store的一致性。
上面返回的 script標籤,裏面已經注水,將在服務端獲取到的數據給到了全局window下的context屬性,在初始化客戶端 store時候咱們給它脫水。初始化渲染使用服務端獲取的數據~

  • 這裏注意,在組件的componentDidMount生命週期中發送ajax等獲取數據時候,先判斷下狀態樹中有沒有數據,若是有數據,那麼就不要重複發送請求,致使資源浪費。
  • 多層級路由SSR

  • 入口文件路由部分改爲:

  • 後續可能有利用loader進行CSS的服務端渲染以及helmet的動態meta, title標籤進行SEO優化等,今天時間緊促,就不繼續寫SSR了。

構建Electron極度複雜,超大數據的應用。

須要用到技術,sqlite,PWA,web work,原生Node.js,react-window,react-lazyload,C++插件等
  • 第一個提到的是sqlite,嵌入式關係型數據庫,輕量型無入侵性,標準的sql語句,這裏不作過多介紹。
  • PWA,漸進性式web應用,這裏使用webpack4的插件,進行快速使用,對於一些數據內容不須要存儲數據庫的,可是卻想要一次拉取,屢次複用,那麼可使用這個配置

serverce work也有它的一套生命週期

  • 一般咱們若是要使用Service Worker基本就是如下幾個步驟:
  • 首先咱們須要在頁面的 JavaScript 主線程中使用 serviceWorkerContainer.register() 來註冊 Service Worker ,在註冊的過程當中,瀏覽器會在後臺啓動嘗試 Service Worker 的安裝步驟。
  • 若是註冊成功,Service Worker 在 ServiceWorkerGlobalScope 環境中運行;這是一個特殊的 worker context,與主腳本的運行線程相獨立,同時也沒有訪問 DOM 的能力。
  • 後臺開始安裝步驟, 一般在安裝的過程當中須要緩存一些靜態資源。若是全部的資源成功緩存則安裝成功,若是有任何靜態資源緩存失敗則安裝失敗,在這裏失敗的沒關係,會自動繼續安裝直到安裝成功,若是安裝不成功沒法進行下一步 — 激活 Service Worker。
  • 開始激活 Service Worker,必需要在 Service Worker 安裝成功以後,才能開始激活步驟,當 Service Worker 安裝完成後,會接收到一個激活事件(activate event)。激活事件的處理函數中,主要操做是清理舊版本的 Service Worker 腳本中使用資源。
  • 激活成功後 Service Worker 能夠控制頁面了,可是隻針對在成功註冊了 Service Worker 後打開的頁面。也就是說,頁面打開時有沒有 Service Worker,決定了接下來頁面的生命週期內受不受 Service Worker 控制。因此,只有當頁面刷新後,以前不受 Service Worker 控制的頁面纔有可能被控制起來。
直接上代碼,存儲全部 js文件和圖片 //實際的存儲根據自身須要,並非越多越好。

  • PWA並不只僅這些功能,它的功能很是強大,有興趣的能夠去lavas看看,PWA技術對於常常訪問的老客戶來講,首屏渲染提高很是大,特別在移動端,能夠添加到桌面保存。666啊~,在pc端更多的是緩存處理文件~
  • 使用react-lazyload,懶加載你的視窗初始看不見的組件或者圖片

  • 懶加載組件

大數據React渲染,擁有讓應用擁有60FPS -很是核心的一點優化

  • List長列表

  • react-virtualized-auto-sizer和windowScroll配合一塊兒使用,達到頁面複雜效果+大數據渲染保持60FPS。上面的官網裏有介紹這些組件~

高計算量的工做交給web wrok線程

  • 這段代碼中變量first和second表明2個input元素;它們當中任意一個的值發生改變時,myWorker.postMessage([first.value,second.value])會將這2個值組成數組發送給worker。你能夠在消息中發送許多你想發送的東西。
  • 在worker中接收到消息後,咱們能夠寫這樣一個事件處理函數代碼做爲響應(worker.js):

  • onmessage處理函數容許咱們在任什麼時候刻,一旦接收到消息就能夠執行一些代碼,代碼中消息自己做爲事件的data屬性進行使用。這裏咱們簡單的對這2個數字做乘法處理並再次使用postMessage()方法,將結果回傳給主線程。
  • 回到主線程,咱們再次使用onmessage以響應worker回傳的消息:

  • 在這裏咱們獲取消息事件的data,而且將它設置爲result的textContent,因此用戶能夠直接看到運算的結果。
  • 注意:在主線程中使用時,onmessage和postMessage() 必須掛在worker對象上,而在worker中使用時不用這樣作。緣由是,在worker內部,worker是有效的全局做用域。
  • 注意:當一個消息在主線程和worker之間傳遞時,它被複制或者轉移了,而不是共享。
開啓 web work線程,其實也會損耗必定的主線程的性能,可是大量計算的工做交給它也何嘗不可,其實 Node.jsjavaScript都不適合作大量計算工做,這點有目共睹,尤爲是 js引擎和 GUI渲染線程互斥的狀況存在。

充分合理利用ReactFeber架構diff算法優化項目

  • requestAnimationFrame調用高優先級任務,中斷調度階段的遍歷,因爲React的新版本調度階段是擁有三根指針的可中斷的鏈表遍歷,因此這樣既不影響下面的遍歷,也不影響用戶交互等行爲。

使用 requestAnimationFrame也能夠更好的讓瀏覽器保持60幀的動畫
  • 使用requestAnimationFrame,當頁面處於未激活的狀態下,該頁面的屏幕刷新任務會被系統暫停,因爲requestAnimationFrame保持和屏幕刷新同步執行,因此也會被暫停。當頁面被激活時,動畫從上次停留的地方繼續執行,節約 CPU 開銷。
  • 一個刷新間隔內函數執行屢次時沒有意義的,由於顯示器每 16.7ms 刷新一次,屢次繪製並不會在屏幕上體現出來
  • 在高頻事件(resize,scroll等)中,使用requestAnimationFrame能夠防止在一個刷新間隔內發生屢次函數執行,這樣保證了流暢性,也節省了函數執行的開銷 某些狀況下能夠直接使用requestAnimationFrame替代 Throttle 函數,都是限制回調函數執行的頻率
  • requestIdleCallback,這個API目前兼容性不太好,可是在Electron開發中,可使用,二者仍是有區別的,並且這兩個api用好了能夠解決不少複雜狀況下的問題~。固然你也能夠用上面的api封裝這個api,也並非很複雜。
  • 當關注用戶體驗,不但願由於一些不重要的任務(如統計上報)致使用戶感受到卡頓的話,就應該考慮使用requestIdleCallback。由於requestIdleCallback回調的執行的前提條件是當前瀏覽器處於空閒狀態。

  • 圖中一幀包含了用戶的交互、js的執行、以及requestAnimationFrame的調用,佈局計算以及頁面的重繪等工做。假如某一幀裏面要執行的任務很少,在不到16ms(1000/60)的時間內就完成了上述任務的話,那麼這一幀就會有必定的空閒時間,這段時間就剛好能夠用來執行requestIdleCallback的回調,以下圖所示:

使用preloadprefetch,dns-prefetch等指定提早請求指定文件,或者根據狀況,瀏覽器自行決定是否提早dns預解析或者按需請求某些資源。

  • 這裏也能夠webpack4插件實現,目前京東在使用這個方案~

對指定js文件延遲加載~

  • script標籤,加上async標籤,遇到此標籤,先去請求,可是不阻塞解析html等文件~,請求回來就立馬加載

  • script標籤,加上defer標籤,延遲加載,可是必須在全部腳本加載完畢後纔會加載它,可是這個標籤有bug,不肯定可否準時加載。通常只給一個

寫這篇時間太耗時間, React-native的以及一些細節,後面再補充

原創不易,感受有收穫,請幫忙關注一下公衆號,點個在看,謝謝java

相關文章
相關標籤/搜索