日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

React 移动 web 极致优化

發(fā)布時間:2025/5/22 编程问答 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 React 移动 web 极致优化 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

原文地址:https://github.com/lcxfs1991/...

最近一個季度,我們都在為手Q家校群做重構優(yōu)化,將原有那套問題不斷的框架換掉。經過一些斟酌,決定使用react 進行重構。選擇react,其實也主要是因為它具有下面的三大特性。

React的特性

1. Learn once, write anywhere

學習React的好處就是,學了一遍之后,能夠寫web, node直出,以及native,能夠適應各種紛繁復雜的業(yè)務。需要輕量快捷的,直接可以用Reactjs;需要提升首屏時間的,可以結合React Server Render;需要更好的性能的,可以上React Native。

但是,這其實暗示學習的曲線非常陡峭。單單是Webpack+ React + Redux就已夠一個入門者夠嗆,更何況還要兼顧直出和手機客戶端。不是一般人能hold住所有端。

2. Virtual Dom

Virtual Dom(下稱vd)算是React的一個重大的特色,因為Facebook宣稱由于vd的幫助,React能夠達到很好的性能。是的,Facebook說的沒錯,但只說了一半,它說漏的一半是:“除非你能正確的采用一系列優(yōu)化手段”。

3. 組件化

另一個被大家所推崇的React優(yōu)勢在于,它能令到你的代碼組織更清晰,維護起來更容易。我們在寫的時候也有同感,但那是直到我們踩了一些坑,并且漸漸熟悉React+ Redux所推崇的那套代碼組織規(guī)范之后。

那么?

上面的描述不免有些先揚后抑的感覺,那是因為往往作為React的剛入門者,都會像我們初入的時候一樣,對React滿懷希望,指意它幫我們做好一切,但隨著了解的深入,發(fā)現(xiàn)需要做一些額外的事情來達到我們的期待。

對React的期待

初學者對React可能滿懷期待,覺得React可能完爆其它一切框架,甚至不切實際地認為React可能連原生的渲染都能完爆——對框架的狂熱確實會出現(xiàn)這樣的不切實際的期待。讓我們來看看React的官方是怎么說的。React官方文檔在Advanced Performanec這一節(jié),這樣寫道:

One of the first questions people ask when considering React for a project is whether their application will be as fast and responsive as an equivalent non-React version

顯然React自己也其實只是想盡量達到跟非React版本相若的性能。React在減少重復渲染方面確實是有一套獨特的處理辦法,那就是vd,但顯示在首次渲染的時候React絕無可能超越原生的速度,或者一定能將其它的框架比下去。因此,我們在做優(yōu)化的時候,可的期待的東西有:

  • 首屏時間可能會比較原生的慢一些,但可以嘗試用React Server Render (又稱Isomorphic)去提高效率

  • 用戶進行交互的時候,有可能會比原生的響應快一些,前提是你做了一些優(yōu)化避免了浪費性能的重復渲染。

以手Q家校群功能頁React重構優(yōu)化為例

手Q家校群功能頁主要由三個頁面構成,分別是列表頁、布置頁和詳情頁。列表頁已經重構完成并已發(fā)布,布置頁已重構完畢準備提測,詳情頁正在重構。與此同時我們已完成對列表頁的同構直出優(yōu)化,并已正在做React Native優(yōu)化的鋪墊。

這三個頁面的重構其實覆蓋了不少頁面的案例,所以還是蠻有代表性的,我們會將重構之中遇到的一些經驗穿插在文章里論述。

在手Q家校群重構之前,其實我們已經做了一版PC家校群。當時將native的頁面全部web化,直接就采用了React比較常用的全家桶套裝:

  • 構建工具 => gulp + webpack

  • 開發(fā)效率提升 => redux-dev-tools + hot-reload

  • 統(tǒng)一數(shù)據(jù)管理=> redux

  • 性能提升 => immutable + purerender

  • 路由控制器 => react-router(手Q暫時沒采用)

為什么我們在優(yōu)化的時候主要講手Q呢?畢竟PC的性能在大部份情況下已經很好,在PC上一些存在的問題都被PC良好的性能掩蓋下去。手機的性能不如PC,因此有更多有價值的東西深挖。開發(fā)的時候我就跟同事開玩笑說:“沒做過手機web優(yōu)化的都真不好意思說自己做過性能優(yōu)化啊“。

構建針對React做的優(yōu)化

我在《性能優(yōu)化三部曲之一——構建篇》提出,“通過構建,我們可以達成開發(fā)效率的提升,以及對項目最基本的優(yōu)化”。在進行React重構優(yōu)化的過程中,構建對項目的優(yōu)化作用必不可少。在本文暫時不贅述,我另外開辟了一篇《webpack使用優(yōu)化(react篇)》進行具體論述。

開發(fā)效率提升工具

在PC端使用Redux的時候,我們都很喜歡使用Redux-Devtools來查看Redux觸發(fā)的action,以及對應的數(shù)據(jù)變化。PC端使用的時候,我們習慣擺在右邊。但移動端的屏幕較少,因此家校群項目使用的時候放在底部,而且由于性能問題,我們在constant里設一個debug參數(shù),然后在chrome調試時打開,移動端非必須的時候關閉。否則,它會導致移動web的渲染比較低下。

數(shù)據(jù)管理及性能優(yōu)化

Redux統(tǒng)一管理數(shù)據(jù)

這一部份算是重頭戲吧。React作為View層的框架,已經通過vd幫助我們解決重復渲染的問題。但vd是通過看數(shù)據(jù)的前后差異去判斷是否要重復渲染的,但React并沒有幫助我們去做這層比較。因此我們需要使用一整套數(shù)據(jù)管理工具及對應的優(yōu)化方法去達成。在這方法,我們選擇了Redux。

Redux整個數(shù)據(jù)流大體可以用下圖來描述:

Redux這個框架的好處在于能夠統(tǒng)一在自己定義的reducer函數(shù)里面去進行數(shù)據(jù)處理,在View層中只需要通過事件去處觸發(fā)一些action就可以改變地應的數(shù)據(jù),這樣能夠使數(shù)據(jù)處理和dom渲染更好地分離,而避免手動地去設置state。

在重構的時候,我們傾向于將功能類似的數(shù)據(jù)歸類到一起,并建立對應的reducer文件對數(shù)據(jù)進行處理。如下圖,是手Q家校群布置頁的數(shù)據(jù)結構。有些大型的SPA項目可能會將初始數(shù)據(jù)分開在不同的reducer文件里,但這里我們傾向于歸到一個store文件,這樣能夠清晰地知道整個文件的數(shù)據(jù)結構,也符合Redux想統(tǒng)一管理數(shù)據(jù)的想法。然后數(shù)據(jù)的每個層級與reducer文件都是一一對應的關系。

重復渲染導致卡頓

這套React + Redux的東西在PC家校群頁面上用得很歡樂, 以至于不用怎么寫shouldComponentUpdate都沒遇到過什么性能問題。但放到移動端上,我們在列表頁重構的時候就馬上遇到卡頓的問題了。

什么原因呢?是重復渲染導致的!!!!!!

說好的React vd可以減少重復渲染呢?!!!

請別忘記前提條件!!!!

你可以在每個component的render里,放一個console.log("xxx component")。然后觸發(fā)一個action,在優(yōu)化之前,幾乎全部的component都打出這個log,表明都重復渲染了。

React性能的救星Immutablejs


(網圖,引用的文章太多以致于不知道哪篇才是出處)

上圖是React的生命周期,還沒熟悉的同學可以去熟悉一下。因為其中的shouldComponentUpdate是優(yōu)化的關鍵。React的重復渲染優(yōu)化的核心其實就是在shouldComponentUpdate里面做數(shù)據(jù)比較。在優(yōu)化之前,shouldComponentUpdate是默認返回true的,這導致任何時候觸發(fā)任何的數(shù)據(jù)變化都會使component重新渲染。這必然會導致資源的浪費和性能的低下——你可能會感覺比較原生的響應更慢。

這時你開始懷疑這世界——是不是Facebook在騙我。

當時遇到這個問題我的開始翻閱文檔,也是在Facebook的Advanced Performance一節(jié)中找到答案:Immutablejs。這個框架已被吹了有一年多了吧,吹這些框架的人理解它的原理,但不一定實踐過——因為作為一線移動端開發(fā)者,打開它的github主頁看dist文件,50kb,我就已經打退堂鼓了。只是遇到了性能問題,我們才再認真地去了解一遍。

Immutable這個的意思就是不可變,Immutablejs就是一個生成數(shù)據(jù)不可變的框架。一開始你并不理解不可變有什么用。最開始的時候Immutable這種數(shù)據(jù)結構是為了解決數(shù)據(jù)鎖的問題,而對于js,就可以借用來解決前后數(shù)據(jù)比較的問題——因為同時Immutablejs還提供了很好的數(shù)據(jù)比較方法——Immutable.is()。小結一下就是:

  • Immutablejs本身就能生成不可變數(shù)據(jù),這樣就不需要開發(fā)者自己去做數(shù)據(jù)深拷貝,可以直接拿prevProps/prevState和nextProps/nextState來比較。

  • Immutable本身還提供了數(shù)據(jù)的比較方法,這樣開發(fā)者也不用自己去寫數(shù)據(jù)深比較的方法。

說到這里,已萬事俱備了。那東風呢?我們還欠的東風就是應該在哪里寫這個比較。答案就是shouldComponentUpdate。這個生命周期會傳入nextProps和nextState,可以跟component當前的props和state直接比較。這個就可以參考pure-render的做法,去重寫shouldComponentUpdate,在里面寫數(shù)據(jù)比較的邏輯。

其中一位同事polarjiang利用Immutablejs的is方法,參考pure-render-decorator寫了一個immutable-pure-render-decorator。

那具體怎么使用immutable + pure-render呢?

對于immutable,我們需要改寫一下reducer functions里面的處理邏輯,一律換成Immutable的api。

至于pure-render,若是es5寫法,可以用使mixin;若是es6/es7寫法,需要使用decorator,在js的babel loader里面,新增plugins: [‘transform-decorators-legacy’]。其es6的寫法是

@pureRender export default class List extends Component { ... }

Immutablejs帶來的一些問題

不重新渲染

你可能會想到Immutable能減少無謂的重新渲染,但可能沒想過會導致頁面不能正確地重新渲染。目前列表頁在老師進入的時候是有2個tab的,tab的切換會讓列表也切換。目前手Q的列表頁學習PC的列表頁,兩個列表共用一套dom結構(因為除了作業(yè)布置者名字之外,兩個列表一模一樣)。上了Immutablejs之后,當碰巧“我發(fā)布的“列表和”全部“列表開頭的幾個作業(yè)都是同一個人布置的時候,列表切換就不重新渲染了。

引入immutable和pureRender后,render里的JSX注意一定不要有同樣的key(如兩個列表,有重復的數(shù)據(jù),此時以數(shù)據(jù)id來作為key就不太合適,應該要用數(shù)據(jù)id + 列表類型作為key),會造成不渲染新數(shù)據(jù)情況。列表頁目前的處理辦法是將key值換成id + listType。


(列表頁兩個列表的切換)

這樣寫除了保證在父元素那一層知曉數(shù)據(jù)(key值)不同需要重新渲染之外,也保證了React底層渲染知道這是兩組不同的數(shù)據(jù)。在React源文件里有一個ReactChildReconciler.js主要是寫children的渲染邏輯。其中的updateChildren里面有具體如何比較前后children,然后再決定是否要重新渲染。在比較的時候它調用了shouldUpdateReactComponent方法。我們看到它有對key值做比較。在兩個列表中有不同的key,在數(shù)據(jù)相似的情況下,能保證兩者切換的時候能重新渲染。

function shouldUpdateReactComponent(prevElement, nextElement) {var prevEmpty = prevElement === null || prevElement === false;var nextEmpty = nextElement === null || nextElement === false;if (prevEmpty || nextEmpty) {return prevEmpty === nextEmpty;}var prevType = typeof prevElement;var nextType = typeof nextElement;if (prevType === 'string' || prevType === 'number') {return nextType === 'string' || nextType === 'number';} else {return nextType === 'object' && prevElement.type === nextElement.type && prevElement.key === nextElement.key;} }

Immutablejs太大了

上文也提到Immutablejs編譯后的包也有50kb。對于PC端來說可能無所謂,網速足夠快,但對于移動端來說壓力就大了。有人寫了個seamless-immutable,算是簡易版的Immutablejs,只有2kb,只支持Object和Array。

但其實數(shù)據(jù)比較邏輯寫起來也并不難,因此再去review代碼的時候,我決定嘗試自己寫一個,也是這個決定讓我發(fā)現(xiàn)了更多的奧秘。

針對React的這個數(shù)據(jù)比較的深比較deepCompare,要點有2個:

  • 盡量使傳入的數(shù)據(jù)扁平化一點

  • 比較的時候做一些限制,避免溢出棧

先上一下列表頁的代碼,如下圖。這里當時是學習了PC家校群的做法,將component作為props傳入。這里的<Scroll>封裝的是滾動檢測的邏輯,而<List>則是列表頁的渲染,<Empty>是列表為空的時候展示的內容,<Loading>是列表底部加載的顯示橫條。

針對deepCompare的第1個要點,扁平化數(shù)據(jù),我們很明顯就能定位出其中一個問題了。例如<Empty>,我們傳入了props.hw,這個props包括了兩個列表的數(shù)據(jù)。但這樣的結構就會是這樣

props.hw = {listMine: [{...}, {...}, ...],listAll: [{...}, {...}, ...], }

但如果我們提前在傳入之前判斷當前在哪個列表,然后傳入對應列表的數(shù)量,則會像這樣:
props.hw = 20;

兩者比較起來,顯示是后者簡單得多。

針對deepCompare第2點,限制比較的條件。首先讓我們想到的是比較的深度。一般而言,對于Object和Array數(shù)據(jù),我們都需要遞歸去進行比較,出于性能的考慮,我們都會限制比較的深度。

除此之外,我們回顧一下上面的代碼,我們將幾個React component作為props傳進去了,這會在shouldComponentUpdate里面顯示出來。這些component的結構大概如下:

$$typeof // 類型 _owner // 父組件 _self: // 僅開發(fā)模式出現(xiàn) _source: // 僅開發(fā)模式出現(xiàn) _store // 僅開發(fā)模式出現(xiàn) key // 組件的key屬性值 props // 從傳入的props ref // 組件的ref屬性值 type 本組件ReactComponent

因此,針對component的比較,有一些是可以忽略的,例如$$typeof, _store, _self, _source, _owner。type這個比較復雜,可以比較,但僅限于我們定好的比較深度。如果不做這些忽略,這個深比較將會比較消耗性能。關于這個deepCompare的代碼,我放在了pure-render-deepCompare-decorator。

不過其實,將component當作props傳入更為靈活,而且能夠增加組件的復用性,但從上面看來,是比較消耗性能的。看了官方文檔之后,我們嘗試換種寫法,主要就是采用<Scroll>包裹<List>的做法,然后用this.props.children在<Scroll>里面渲染,并將<Empty>, <Loading>抽出來。

本以為React可能會對children這個props有什么特殊處理,但它依然是將children當作props,傳入shouldComponentUpdate,這就迫使父元素<Scroll>要去判斷是否要重新渲染,進而跳到子無素<List>再去判斷是否進一步進行渲染。

那<Scroll>究竟要不要去做這重判斷呢?針對列表頁這種情況,我們覺得可以暫時不做,由于<Scroll>包裹的元素不多,<Scroll>可以先重復渲染,然后再交由子元素<List>自己再去判斷。這樣我們對pure-render-deepCompare-decorator要進行一些修改,當輪到props.children判斷的時候,我們要求父元素直接重新渲染,這樣就能交給子元素去做下一步的處理。

如果<Scroll>包裹的只有<List>還好,如果還有像<Empty>, <Loading>甚至其它更多的子元素,那<Scroll>重新渲染會觸發(fā)其它子元素去運算,判斷自己是否要做重新渲染,這就造成了浪費。react的官方論壇上已經有人提出,React的將父子元素的重復渲染的決策都放在shouldComponentUpdate,可能導致了耦合Shouldcomponentupdate And Children。

lodash.merge可以解決大部份場景

此段更新于2016年6月30日
由于immutable的大小問題一直縈繞頭上,久久不得散去,因此再去找尋其它的方案。后面決定嘗試一下lodash.merge,并用上之前自己寫的pureRender。在渲染性能上還可以接受,在僅比immutable差一點點(后面會披露具體數(shù)據(jù)),但卻帶來了30kb的減包。

性能優(yōu)化小Tips

這里歸納了一些其它性能優(yōu)化的小Tips

請慎用setState,因其容易導致重新渲染

既然將數(shù)據(jù)主要交給了Redux來管理,那就盡量使用Redux管理你的數(shù)據(jù)和狀態(tài)state,除了少數(shù)情況外,別忘了shouldComponentUpdate也需要比較state。

請將方法的bind一律置于constructor

Component的render里不動態(tài)bind方法,方法都在constructor里bind好,如果要動態(tài)傳參,方法可使用閉包返回一個最終可執(zhí)行函數(shù)。如:showDelBtn(item) { return (e) => {}; }。如果每次都在render里面的jsx去bind這個方法,每次都要綁定會消耗性能。

請只傳遞component需要的props

傳得太多,或者層次傳得太深,都會加重shouldComponentUpdate里面的數(shù)據(jù)比較負擔,因此,也請慎用spread attributes(<Component {...props} />)。

請盡量使用const element

這個用法是工業(yè)聚在React討論微信群里教會的,我們可以將不怎么變動,或者不需要傳入狀態(tài)的component寫成const element的形式,這樣能加快這個element的初始渲染速度。

路由控制與拆包

當項目變得更大規(guī)模與復雜的時候,我們需要設計成SPA,這時路由管理就非常重要了,這使特定url參數(shù)能夠對應一個頁面。

PC家校群整個設計是一個中型的SPA,當js bundle太大的時候,需要拆分成幾個小的bundle,進行異步加載。這時可以用到webpack的異步加載打包功能,require。

在重構手Q家校群布置頁的時候,我們有不少的浮層,列表有布置頁內容主浮層、同步到多群浮層、科目管理浮層以及指定群成員浮層。這些完全可以使用react-router進行管理。但是由于當時一早使用了Immutablejs,js bundle已經比較大,我們就不打算使用react-router了。但后面仍然發(fā)現(xiàn)包比重構前要大一些,因此為了保證首屏時間不慢于重構前,我們希望在不用react-router的情況下進行分包,其實也并不難,如下面2幅圖:

首先在切換浮層方法里面,使用require.ensure,指定要加載哪個包。
在setComponent方法里,將component存在state里面。
在父元素的渲染方法里,當state有值的時候,就會自動渲染加載回來的component。

性能數(shù)據(jù)

首屏可交互時間

目前只有列表頁發(fā)布外網了,我們比較了優(yōu)化前后的首屏可交互時間,分別有18%和5.3%的提升。

渲染FPS

更新于2016年7月2日

Android

React重構后第一版,當時還沒做任何的優(yōu)化,發(fā)現(xiàn)平均FPS只有22(雖然Android的肉眼感受不出來),而后面使用Immutable或者Lodash.merge都非常接近,能達到42或以上。而手機QQ可接受的FPS最少值是30FPS。因此使用Immutable和Lodash.merge的優(yōu)化還是相當明顯的。

  • 重構后第一版

  • Immutable

  • Lodash.merge

iOS

在iOS上的fps差距尤為明顯。重構后第一版,拉了大概5屏之后,肉眼會有卡頓的感覺,拉到了10屏之后,數(shù)據(jù)開始掉到了20多30。而Immutable和Lodash.merge則大部份時間保持在50fps以上,很多時候還能達到非常流暢的60fps。

  • 重構后第一版

  • Immutable

  • Lodash.merge

Chrome模擬器

用Chrome模擬器也能看出一些端倪。在Scripting方面,Immutable和Lodash.merge的耗時是最少的,約700多ms,而重構后的第一版則需要1220ms。Lodash.merge在rendering和painting上則沒占到優(yōu)勢,但Immutable則要比其它兩個要少30% - 40%。由于測試的時候是在PC端,PC端的性能又極好,所以不管是肉眼,還是數(shù)據(jù),對于不是很復雜的需求,總體的渲染性能看不出非常明顯的差距。

  • 重構后第一版

  • Immutable

  • Lodash.merge

從上面的數(shù)據(jù)看來,在移動端使用Immutable和Lodash.merge相對于不用,會有較大的性能優(yōu)勢,但Immutable相對于Lodash.merge在我們需求情景下暫時沒看出明顯的優(yōu)勢,筆者估計可能是由于項目數(shù)據(jù)規(guī)模不大,結構不復雜,因此Immutable的算法優(yōu)勢并沒有充分發(fā)揮出來。

測試注明

Android端測試FPS是使用了騰訊開發(fā)的GT隨身調。而iOS則使用了Macbook里xCode自帶的instrument中的animation功能。Chrome模擬器則使用了Chrome的timeline。測試的方式是勻速滾動列表,拉出數(shù)據(jù)進行渲染。

React性能優(yōu)化軍規(guī)

我們在開發(fā)的過程中,將上面所論述的內容,總結成一個基本的軍規(guī),銘記于心,就可以保證React應用的性能不至于太差。

渲染相關

  • 提升級項目性能,請使用immutable(props、state、store)

  • 請pure-render-decorator與immutablejs搭配使用

  • 請慎用setState,因其容易導致重新渲染

  • 謹慎將component當作props傳入

  • 請將方法的bind一律置于constructor

  • 請只傳遞component需要的props,避免其它props變化導致重新渲染(慎用spread attributes)

  • 請在你希望發(fā)生重新渲染的dom上設置可被react識別的同級唯一key,否則react在某些情況可能不會重新渲染。

  • 請盡量使用const element

tap事件

1. 簡單的tap事件,請使用react-tap-event-plugin

開發(fā)環(huán)境時,最好引入webpack的環(huán)境變量(僅在開發(fā)環(huán)境中初始化),在container中初始化。生產環(huán)境的時候,請將plugin跟react打包到一起(需要打包在一起才能正常使用,因為plugin對react有好多依賴),外鏈引入。

目前參考了這個項目的打包方案:

  • https://github.com/hartmamt/r...

  • Facebook官方issue: https://github.com/facebook/r...

  • React-tap-event-plugin github: https://github.com/zilverline...

2. 復雜的tap事件,建議使用tap component

家校群列表頁的每個作業(yè)的tap交互都比較復雜,出了普通的tap之外,還需要long tap和swipe。因此我們只好自己封裝了一個tap component

Debug相關

  • 移動端請慎用redux-devtools,易造成卡頓

  • Webpack慎用devtools的inline-source-map模式
    使用此模式會內聯(lián)一大段便于定位bug的字符串,查錯時可以開啟,不是查錯時建議關閉,否則開發(fā)時加載的包會非常大。

其它

  • 慎用太新的es6語法。
    Object.assign等較新的類庫避免在移動端上使用,會報錯。

Object.assign目前使用object-assign包。或者使用babel-plugin-transform-object-assign插件。會轉換成一個extends的函數(shù):

var _extends = ...;_extends(a, b);

如有錯誤,請斧正!

《新程序員》:云原生和全面數(shù)字化實踐50位技術專家共同創(chuàng)作,文字、視頻、音頻交互閱讀

總結

以上是生活随笔為你收集整理的React 移动 web 极致优化的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。