React的特性
1. Learn once, write anywhere
學(xué)習(xí)React的好處就是,學(xué)了一遍之后妆偏,能夠?qū)憌eb, node直出我磁,以及native,能夠適應(yīng)各種紛繁復(fù)雜的業(yè)務(wù)厌衔。需要輕量快捷的璧帝,直接可以用Reactjs;需要提升首屏?xí)r間的富寿,可以結(jié)合React Server Render睬隶;需要更好的性能的,可以上React Native页徐。
但是苏潜,這其實(shí)暗示學(xué)習(xí)的曲線非常陡峭。單單是Webpack+ React + Redux就已夠一個(gè)入門(mén)者夠嗆变勇,更何況還要兼顧直出和手機(jī)客戶端恤左。不是一般人能hold住所有端。
2. Virtual Dom
Virtual Dom(下稱vd)算是React的一個(gè)重大的特色搀绣,因?yàn)镕acebook宣稱由于vd的幫助飞袋,React能夠達(dá)到很好的性能。是的链患,F(xiàn)acebook說(shuō)的沒(méi)錯(cuò)巧鸭,但只說(shuō)了一半,它說(shuō)漏的一半是:“除非你能正確的采用一系列優(yōu)化手段”麻捻。
3. 組件化
另一個(gè)被大家所推崇的React優(yōu)勢(shì)在于纲仍,它能令到你的代碼組織更清晰览闰,維護(hù)起來(lái)更容易。我們?cè)趯?xiě)的時(shí)候也有同感巷折,但那是直到我們踩了一些坑压鉴,并且漸漸熟悉React+ Redux所推崇的那套代碼組織規(guī)范之后。
上面的描述不免有些先揚(yáng)后抑的感覺(jué)锻拘,那是因?yàn)橥鳛镽eact的剛?cè)腴T(mén)者油吭,都會(huì)像我們初入的時(shí)候一樣,對(duì)React滿懷希望署拟,指意它幫我們做好一切婉宰,但隨著了解的深入,發(fā)現(xiàn)需要做一些額外的事情來(lái)達(dá)到我們的期待推穷。
對(duì)React的期待
初學(xué)者對(duì)React可能滿懷期待心包,覺(jué)得React可能完爆其它一切框架, 甚至不切實(shí)際地認(rèn)為React可能連原生的渲染都能完爆——對(duì)框架的 狂熱確實(shí)會(huì)出現(xiàn)這樣的不切實(shí)際的期待馒铃。讓我們來(lái)看看React的官方是怎么說(shuō)的蟹腾。React官方文檔在Advanced Performanec這一節(jié),這樣寫(xiě)道:
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自己也其實(shí)只是想盡量達(dá)到跟非React版本相若的性能区宇。React在減少重復(fù)渲染方面確實(shí)是有一套獨(dú)特的處理辦法娃殖,那就是vd (Virtual Dom),但顯示在首次渲染的時(shí)候React絕無(wú)可能超越原生的速度议谷,或者一定能將其它的框架比下去炉爆。因此,我們?cè)谧鰞?yōu)化的時(shí)候卧晓,可的期待的東西有:
- 首屏?xí)r間可能會(huì)比原生的慢一些芬首,但可以嘗試用React Server Render (又稱Isomorphic)去提高效率
- 用戶進(jìn)行交互的時(shí)候,有可能會(huì)比原生的響應(yīng)快一些逼裆,前提是你做了一些優(yōu)化避免了浪費(fèi)性能的重復(fù)渲染郁稍。
構(gòu)建針對(duì)React做的優(yōu)化
在PC端使用Redux的時(shí)候,我們都很喜歡使用Redux-Devtools來(lái)查看Redux觸發(fā)的action波附,以及對(duì)應(yīng)的數(shù)據(jù)變化艺晴。PC端使用的時(shí)候,我們習(xí)慣擺在右邊掸屡。但移動(dòng)端的屏幕較少封寞,因此家校群項(xiàng)目使用的時(shí)候放在底部,而且由于性能問(wèn)題仅财,我們?cè)赾onstant里設(shè)一個(gè)debug參數(shù)狈究,然后在chrome調(diào)試時(shí)打開(kāi),移動(dòng)端非必須的時(shí)候關(guān)閉盏求。否則抖锥,它會(huì)導(dǎo)致移動(dòng)web的渲染比較低下亿眠。
數(shù)據(jù)管理及性能優(yōu)化
Redux統(tǒng)一管理數(shù)據(jù)
這一部份算是重頭戲吧。React作為View層的框架磅废,已經(jīng)通過(guò)vd (Virtual Dom)幫助我們解決重復(fù)渲染的問(wèn)題纳像。但vd (Virtual Dom)是通過(guò)看數(shù)據(jù)的前后差異去判斷是否要重復(fù)渲染的,但React并沒(méi)有幫助我們?nèi)プ鲞@層比較拯勉。因此我們需要使用一整套數(shù)據(jù)管理工具及對(duì)應(yīng)的優(yōu)化方法去達(dá)成竟趾。在這方法,我們選擇了Redux宫峦。
Redux整個(gè)數(shù)據(jù)流大體可以用下圖來(lái)描述:
Redux這個(gè)框架的好處在于能夠統(tǒng)一在自己定義的reducer函數(shù)里面去進(jìn)行數(shù)據(jù)處理岔帽,在View層中只需要通過(guò)事件去處觸發(fā)一些action就可以改變地應(yīng)的數(shù)據(jù),這樣能夠使數(shù)據(jù)處理和dom渲染更好地分離导绷,而避免手動(dòng)地去設(shè)置state犀勒。
在重構(gòu)的時(shí)候,我們傾向于將功能類似的數(shù)據(jù)歸類到一起妥曲,并建立對(duì)應(yīng)的reducer文件對(duì)數(shù)據(jù)進(jìn)行處理贾费。如下圖,是手Q家校群布置頁(yè)的數(shù)據(jù)結(jié)構(gòu)逾一。有些大型的SPA項(xiàng)目可能會(huì)將初始數(shù)據(jù)分開(kāi)在不同的reducer文件里铸本,但這里我們傾向于歸到一個(gè)store文件肮雨,這樣能夠清晰地知道整個(gè)文件的數(shù)據(jù)結(jié)構(gòu)遵堵,也符合Redux想統(tǒng)一管理數(shù)據(jù)的想法。然后數(shù)據(jù)的每個(gè)層級(jí)與reducer文件都是一一對(duì)應(yīng)的關(guān)系怨规。
重復(fù)渲染導(dǎo)致卡頓
這套R(shí)eact + Redux的東西在PC家校群頁(yè)面上用得很歡樂(lè)陌宿, 以至于不用怎么寫(xiě)shouldComponentUpdate都沒(méi)遇到過(guò)什么性能問(wèn)題。但放到移動(dòng)端上波丰,我們?cè)诹斜眄?yè)重構(gòu)的時(shí)候就馬上遇到卡頓的問(wèn)題了壳坪。
什么原因呢?是重復(fù)渲染導(dǎo)致的j獭K!H移铩P恰!
說(shuō)好的React vd (Virtual Dom) 可以減少重復(fù)渲染呢先馆?7⒖颉!煤墙!
請(qǐng)別忘記前提條件C饭摺O苡怠!铣减!
你可以在每個(gè)component的render里她君,放一個(gè)console.log("xxx component")。然后觸發(fā)一個(gè)action葫哗,在優(yōu)化之前犁河,幾乎全部的component都打出這個(gè)log,表明都重復(fù)渲染了魄梯。
React性能的救星Immutablejs
上圖是React的生命周期桨螺,還沒(méi)熟悉的同學(xué)可以去熟悉一下。因?yàn)槠渲械膕houldComponentUpdate是優(yōu)化的關(guān)鍵酿秸。React的重復(fù)渲染優(yōu)化的核心其實(shí)就是在shouldComponentUpdate里面做數(shù)據(jù)比較灭翔。在優(yōu)化之前,shouldComponentUpdate是默認(rèn)返回true的辣苏,這導(dǎo)致任何時(shí)候觸發(fā)任何的數(shù)據(jù)變化都會(huì)使component重新渲染肝箱。這必然會(huì)導(dǎo)致資源的浪費(fèi)和性能的低下——你可能會(huì)感覺(jué)比較原生的響應(yīng)更慢。
這時(shí)你開(kāi)始懷疑這世界——是不是Facebook在騙我稀蟋。
當(dāng)時(shí)遇到這個(gè)問(wèn)題我的開(kāi)始翻閱文檔煌张,也是在Facebook的Advanced Performance一節(jié)中找到答案:Immutablejs。這個(gè)框架已被吹了有一年多了吧退客,吹這些框架的人理解它的原理骏融,但不一定實(shí)踐過(guò)——因?yàn)樽鳛橐痪€移動(dòng)端開(kāi)發(fā)者,打開(kāi)它的github主頁(yè)看dist文件萌狂,50kb伦腐,我就已經(jīng)打退堂鼓了秆麸。只是遇到了性能問(wèn)題国旷,我們才再認(rèn)真地去了解一遍薇芝。
Immutable這個(gè)的意思就是不可變,Immutablejs就是一個(gè)生成數(shù)據(jù)不可變的框架务傲。一開(kāi)始你并不理解不可變有什么用凉当。最開(kāi)始的時(shí)候Immutable這種數(shù)據(jù)結(jié)構(gòu)是為了解決數(shù)據(jù)鎖的問(wèn)題,而對(duì)于js售葡,就可以借用來(lái)解決前后數(shù)據(jù)比較的問(wèn)題——因?yàn)橥瑫r(shí)Immutablejs還提供了很好的數(shù)據(jù)比較方法——Immutable.is()看杭。小結(jié)一下就是:
- Immutablejs本身就能生成不可變數(shù)據(jù),這樣就不需要開(kāi)發(fā)者自己去做數(shù)據(jù)深拷貝天通,可以直接拿prevProps/prevState和nextProps/nextState來(lái)比較泊窘。
- Immutable本身還提供了數(shù)據(jù)的比較方法,這樣開(kāi)發(fā)者也不用自己去寫(xiě)數(shù)據(jù)深比較的方法。
說(shuō)到這里烘豹,已萬(wàn)事俱備了瓜贾。那東風(fēng)呢?我們還欠的東風(fēng)就是應(yīng)該在哪里寫(xiě)這個(gè)比較携悯。答案就是shouldComponentUpdate祭芦。這個(gè)生命周期會(huì)傳入nextProps和nextState,可以跟component當(dāng)前的props和state直接比較憔鬼。這個(gè)就可以參考pure-render的做法龟劲,去重寫(xiě)shouldComponentUpdate,在里面寫(xiě)數(shù)據(jù)比較的邏輯轴或。
那具體怎么使用immutable + pure-render呢昌跌?
對(duì)于immutable,我們需要改寫(xiě)一下reducer functions里面的處理邏輯照雁,一律換成Immutable的api蚕愤。
至于pure-render,若是es5寫(xiě)法饺蚊,可以用使mixin萍诱;若是es6/es7寫(xiě)法,需要使用decorator污呼,在js的babel loader里面裕坊,新增plugins: [‘transform-decorators-legacy’]。其es6的寫(xiě)法是
@pureRender
export default class List extends Component { ... }
Immutablejs帶來(lái)的一些問(wèn)題
不重新渲染
你可能會(huì)想到Immutable能減少無(wú)謂的重新渲染燕酷,但可能沒(méi)想過(guò)會(huì)導(dǎo)致頁(yè)面不能正確地重新渲染籍凝。
引入immutable和pureRender后,render里的JSX注意一定不要有同樣的key(如兩個(gè)列表悟狱,有重復(fù)的數(shù)據(jù)静浴,此時(shí)以數(shù)據(jù)id來(lái)作為key就不太合適,應(yīng)該要用數(shù)據(jù)id + 列表類型作為key)挤渐,會(huì)造成不渲染新數(shù)據(jù)情況。
Immutablejs太大了
上文也提到Immutablejs編譯后的包也有50kb双絮。對(duì)于PC端來(lái)說(shuō)可能無(wú)所謂浴麻,網(wǎng)速足夠快,但對(duì)于移動(dòng)端來(lái)說(shuō)壓力就大了囤攀。有人寫(xiě)了個(gè)seamless-immutable软免,算是簡(jiǎn)易版的Immutablejs,只有2kb焚挠,只支持Object和Array膏萧。
性能優(yōu)化Tips
這里歸納了一些其它性能優(yōu)化的小Tips
請(qǐng)慎用setState,因其容易導(dǎo)致重新渲染
既然將數(shù)據(jù)主要交給了Redux來(lái)管理,那就盡量使用Redux管理你的數(shù)據(jù)和狀態(tài)state榛泛,除了少數(shù)情況外蝌蹂,別忘了shouldComponentUpdate也需要比較state。
請(qǐng)將方法的bind一律置于constructor
Component的render里不動(dòng)態(tài)bind方法曹锨,方法都在constructor里bind好孤个,如果要?jiǎng)討B(tài)傳參,方法可使用閉包返回一個(gè)最終可執(zhí)行函數(shù)沛简。如:showDelBtn(item) { return (e) => {}; }齐鲤。如果每次都在render里面的jsx去bind這個(gè)方法,每次都要綁定會(huì)消耗性能椒楣。
請(qǐng)只傳遞component需要的props
傳得太多给郊,或者層次傳得太深,都會(huì)加重shouldComponentUpdate里面的數(shù)據(jù)比較負(fù)擔(dān)捧灰,因此丑罪,也請(qǐng)慎用spread attributes(<Component {...props} />)。
請(qǐng)盡量使用const element
我們將不怎么變動(dòng)凤壁,或者不需要傳入狀態(tài)的component寫(xiě)成const element的形式吩屹,這樣能加快這個(gè)element的初始渲染速度。
路由控制與拆包
當(dāng)項(xiàng)目變得更大規(guī)模與復(fù)雜的時(shí)候拧抖,我們需要設(shè)計(jì)成SPA煤搜,這時(shí)路由管理就非常重要了,這使特定url參數(shù)能夠?qū)?yīng)一個(gè)頁(yè)面唧席。
React性能優(yōu)化軍規(guī)
渲染相關(guān)
- 提升級(jí)項(xiàng)目性能擦盾,請(qǐng)使用immutable(props、state淌哟、store)
- 請(qǐng)pure-render-decorator與immutablejs搭配使用
- 請(qǐng)慎用setState迹卢,因其容易導(dǎo)致重新渲染
- 謹(jǐn)慎將component當(dāng)作props傳入
- 請(qǐng)將方法的bind一律置于constructor
- 請(qǐng)只傳遞component需要的props,避免其它props變化導(dǎo)致重新渲染(慎用
{...this.props}
) - 請(qǐng)?jiān)谀阆Ml(fā)生重新渲染的dom上設(shè)置可被react識(shí)別的同級(jí)唯一key徒仓,否則react在某些情況可能不會(huì)重新渲染腐碱。
- 請(qǐng)盡量使用const element
Debug相關(guān)
- 移動(dòng)端請(qǐng)慎用redux-devtools,易造成卡頓
- Webpack慎用devtools的inline-source-map模式
使用此模式會(huì)內(nèi)聯(lián)一大段便于定位bug的字符串掉弛,查錯(cuò)時(shí)可以開(kāi)啟症见,不是查錯(cuò)時(shí)建議關(guān)閉,否則開(kāi)發(fā)時(shí)加載的包會(huì)非常大殃饿。