前言
距離我進(jìn)新公司也有一個(gè)多月,這一個(gè)月的事件使用react寫(xiě)了一個(gè)項(xiàng)目焙蹭,期間斷斷續(xù)續(xù)重構(gòu)了兩三次,目前已經(jīng)完成第一階段測(cè)試嫂伞,也總結(jié)分享一些使用react的一些坑壳嚎。
react
先啰嗦幾句講一下react原理,新人可以認(rèn)真看下末早,老鳥(niǎo)可跳過(guò)烟馅。
react并沒(méi)有像其他如vue,ng一樣采用MVVM模式,所謂MVVM模式然磷,狹義來(lái)說(shuō)就是將模板與數(shù)據(jù)綁定在一起郑趁,當(dāng)數(shù)據(jù)發(fā)生改變時(shí),模板自動(dòng)更新姿搜,這是中間的VM寡润,最左邊的M可以理解為我們看到的頁(yè)面,而最右邊的M可以理解為原始數(shù)據(jù)(例如數(shù)據(jù)庫(kù)數(shù)據(jù))舅柜。
其實(shí)要知道梭纹,這些框架模式歸根結(jié)底的目的希望使代碼更容易開(kāi)發(fā)和維護(hù)。當(dāng)你寫(xiě)一個(gè)小頁(yè)面你不覺(jué)得什么致份,但是當(dāng)你的頁(yè)面越來(lái)越龐大变抽,越來(lái)越復(fù)雜,開(kāi)發(fā)人員走了一批又一批的時(shí)候,你就會(huì)明白了绍载。而如何解決诡宗,現(xiàn)在比較公認(rèn)的理念一個(gè)是組件化,將頁(yè)面拆分成一個(gè)個(gè)組件击儡,其實(shí)拆分成組件的目的并不全是為了復(fù)用塔沃,我覺(jué)得更多是為了維護(hù);一個(gè)就是希望讓?xiě)?yīng)用層的編程能更專(zhuān)注于業(yè)務(wù)邏輯阳谍,那么這些框架都去做了處理蛀柴,減少了大量DOM操作,讓業(yè)務(wù)開(kāi)發(fā)更加專(zhuān)注矫夯。
那么我們看看react采用的是什么原理鸽疾。其實(shí)react采用的方法簡(jiǎn)單粗暴:它并沒(méi)有對(duì)模板做數(shù)據(jù)綁定,而是每當(dāng)數(shù)據(jù)變化時(shí)茧痒,就重新渲染模板。這有一個(gè)很大的好處就是每當(dāng)數(shù)據(jù)變化時(shí)融蹂,對(duì)頁(yè)面來(lái)說(shuō)只有一次IO操作旺订,而單純的雙向綁定更新DOM會(huì)有很多次;但有一個(gè)問(wèn)題超燃,如果只改變了一個(gè)dom的數(shù)據(jù)区拳,整個(gè)模板都會(huì)重新渲染。那react解決的方法是每當(dāng)數(shù)據(jù)改變時(shí)意乓,就進(jìn)行對(duì)比樱调。
那么該如何對(duì)比呢,最簡(jiǎn)單的方法是每當(dāng)數(shù)據(jù)改變届良,就去頁(yè)面獲取相應(yīng)的DOM信息笆凌,然后與現(xiàn)在的DOM信息做比較。這個(gè)方法有個(gè)致命的缺點(diǎn)就是每次有DOM改變士葫,就會(huì)有許多讀取操作乞而,IO操作太多,很影響性能慢显。那么可以通過(guò)空間換時(shí)間的方法爪模,將DOM信息保存起來(lái),就避免了去頁(yè)面獲取信息的IO操作荚藻。那么我們看看一個(gè)真實(shí)DOM有多少數(shù)據(jù)
虛擬DOM
如果我們把所有原生DOM緩存起來(lái)進(jìn)行比較顯然內(nèi)存會(huì)爆炸屋灌,而我們所需要的僅僅是幾個(gè)為數(shù)不多的狀態(tài)信息(例如style啊這些),這時(shí)虛擬DOM應(yīng)運(yùn)而生应狱,如果說(shuō)原生DOM是一塊豬肉共郭,那么虛擬DOM就是這塊豬肉中多精肉,他剔除了那些對(duì)我們來(lái)說(shuō)沒(méi)有太多意義相反還占內(nèi)存的狀態(tài)信息,而只將我們所需要的內(nèi)容留了下來(lái)落塑。那虛擬DOM該如何比較呢纽疟,就涉及到了虛擬DOM的diff算法。
diff算法
簡(jiǎn)單來(lái)說(shuō)兩個(gè)模板就像兩棵樹(shù)一樣憾赁,傳統(tǒng)的樹(shù)對(duì)比的時(shí)間復(fù)雜度是O^3污朽,也就是說(shuō)要整棵樹(shù)遍歷三次,那react根據(jù)DOM的特點(diǎn):跨層級(jí)的操作較少龙考。什么叫跨層級(jí)蟆肆,舉個(gè)例子一個(gè)組件有三個(gè)層級(jí)關(guān)系(嵌套三層),把第二層的div放到第三層就屬于跨層級(jí)操作晦款。這種操作其實(shí)還是比較少的炎功,react官方也不推薦這么寫(xiě)。那么利用這個(gè)特點(diǎn)缓溅,react只diff同層級(jí)的DOM蛇损。這里涉及以下幾種情況
- tag變化(標(biāo)簽變化)
- 屬性變化
第二種沒(méi)什么好說(shuō),就是進(jìn)行同層級(jí)對(duì)比坛怪。這里詳細(xì)說(shuō)一下第一種變化淤齐,react的方法是當(dāng)前層級(jí)往下全部刪除替換,簡(jiǎn)單暴力袜匿。在業(yè)務(wù)開(kāi)發(fā)來(lái)看更啄,你同層級(jí)類(lèi)型都不同了,比如div變成了input居灯,那么你的子組件相同的幾率也較小祭务,因此不如整個(gè)替換簡(jiǎn)單暴力。同時(shí)這也說(shuō)明為什么列表需要key屬性怪嫌,因?yàn)榱斜砗芏嗟膭h除操作對(duì)于react來(lái)說(shuō)是不知道的义锥,它需要一個(gè)key來(lái)了解到底誰(shuí)是誰(shuí)。
踩過(guò)的一些坑
state
作為前端岩灭,拿到原型第一件事就是要與你的產(chǎn)品充分溝通缨该,評(píng)估該項(xiàng)目是否需要引入redux,那么如何確定需要引入redux呢川背,這邊有幾點(diǎn):
- 頁(yè)面之間組件之間通信較多贰拿,且是跨層級(jí)的;
- 頁(yè)面的交互邏輯較復(fù)雜熄云,且經(jīng)常引起多處組件變化膨更。
再然后就是對(duì)state的設(shè)計(jì)。對(duì)于后臺(tái)的返回的數(shù)據(jù)缴允,你并不知道到底哪些是有用的荚守,哪些可能現(xiàn)在沒(méi)用以后有用珍德,因此我的建議是對(duì)于每個(gè)api都將它存在一個(gè)object里。那么組件該如何去獲取矗漾,首先不能全部通過(guò)頂部container獲取依次往下傳锈候,也不能粒度很細(xì)的去一個(gè)個(gè)connect,我的建議是對(duì)于一個(gè)object敞贡,可以用一個(gè)container去connect泵琳,粒度把握主要看你的業(yè)務(wù)需求。
還有一個(gè)要不要全局都使用redux誊役。我的觀(guān)點(diǎn)是否定的获列,我認(rèn)為對(duì)于局部一個(gè)組件內(nèi)的狀態(tài)完全可以通過(guò)setState來(lái)滿(mǎn)足。
圖片處理
首先將圖片做一些劃分蛔垢。例如以500k作為分界击孩,小于500k為小圖片,否則是大圖片鹏漆。對(duì)于小圖片巩梢,我們需要做如下判斷:
- 頁(yè)面是否重復(fù)使用?是就用雪碧圖艺玲,否則轉(zhuǎn)base64.
- 對(duì)于大圖片括蝠,可以進(jìn)行壓縮后使用。
base64可以用webpack的url-loader替換板驳。
舉個(gè)例子
require('url?limit=250!./xxx.png')
//這里就會(huì)使用url-loader又跛,假如圖片小于250碍拆,就會(huì)轉(zhuǎn)為base64
移動(dòng)端適配
對(duì)于適配若治,我所知比較好的方法是利用rem作為單位,將頁(yè)面寬度等分成10個(gè)rem感混,根據(jù)頁(yè)面動(dòng)態(tài)的用js去改變font-size端幼,達(dá)到適配不同瀏覽器的目的。例如愛(ài)瘋寬320px弧满,那么font-size設(shè)置為32px婆跑。那么10rem就是等于整個(gè)屏幕的寬度。但是有一個(gè)特例就是高清屏庭呜,一般高清屏的物理像素是實(shí)際像素的2倍滑进,那么當(dāng)你想顯示一個(gè)寬度為1的邊框時(shí),在普通屏幕是1px募谎,在高清屏可以有0.5px(問(wèn)題是很多瀏覽器不支持扶关,為將0.5px認(rèn)為是0)。雖然都使用1px在兩者屏幕上實(shí)際上是一樣的数冬,但是高清屏里的1px在射雞師眼里是無(wú)法達(dá)到他對(duì)于1的要求的节槐。于是就有一些解決方案,比較簡(jiǎn)單是是使用transform:scale(0.5)。那么還有一種解決方案就是阿里的移動(dòng)端解決方案铜异,原理是將頁(yè)面整體scale縮小哥倔,然后放大font-size,來(lái)保證rem為單位的布局不變揍庄,但是px為單位的會(huì)被縮小咆蒿。
性能提升
首先先確定需求,確實(shí)有這個(gè)需求的時(shí)候再談币绩。
懶加載
webpack其實(shí)會(huì)幫我們做第三方依賴(lài)的懶加載處理蜡秽,那么針對(duì)react,我們可以通過(guò)現(xiàn)成的庫(kù)來(lái)實(shí)現(xiàn)懶加載react-lazyload,或者使用webpack現(xiàn)成的
require.ensure([],()=>{
require('public.js')
})
來(lái)實(shí)現(xiàn)缆镣。
shouldComponentUpdate
其實(shí)這個(gè)鉤子可以極大的幫助我們?nèi)ヌ嵘阅苎客唬捎谒拇嬖冢覀兛梢宰约号袛嗄男┦俏覀兘M件需要的state董瞻,哪些是不需要的寞蚌,那么這就可以阻止react進(jìn)行不必要diff。但是有一個(gè)問(wèn)題就是對(duì)于對(duì)象我們很難去判斷他們是否相等钠糊,那么可以通過(guò)immutableJs的fromJS和is方法來(lái)解決這個(gè)問(wèn)題挟秤。其實(shí)immutableJs的好處遠(yuǎn)不止于此,目前我也尚在填坑中抄伍。
使用不可變數(shù)據(jù)艘刚,可以更好的達(dá)到函數(shù)式編程,不僅利于單元測(cè)試截珍,也更有利于后期維護(hù)整個(gè)大的state攀甚。因?yàn)樗牟豢勺兲攸c(diǎn),我們不會(huì)在不經(jīng)意見(jiàn)不小心改變了state岗喉,而引起不必要的問(wèn)題秋度。
創(chuàng)建組件的痛點(diǎn)
為了使組件中的css作用域相互獨(dú)立,一般采用Css Module
钱床,那么為了使組件看上去更像組件,并且易于后期維護(hù)荚斯,一般我們會(huì)這樣結(jié)構(gòu)化組件文件:
那么對(duì)于每一個(gè)初始的jsx,我們大多會(huì)這樣初始化
import s from './App.scss'
import{Component} from 'react';//得到組件方法
class App extends Component{
render(){
return (
<div className={s.container}>
</div>
)
}
}
export default App;
不難發(fā)現(xiàn)查牌,對(duì)于每一個(gè)組件事期,我都需要去手動(dòng)的創(chuàng)建這些文件和文件夾,而這些操作其實(shí)是重復(fù)且無(wú)意義的勞動(dòng)纸颜,于是我造了一個(gè)小輪子兽泣,一個(gè)命令行小工具,來(lái)解決這個(gè)痛點(diǎn)懂衩。
react-component-maker撞叨,歡迎猛戳點(diǎn)star金踪!
結(jié)語(yǔ)
困了,本寶寶要睡覺(jué)了牵敷,還有的內(nèi)容下次再說(shuō)吧胡岔,再見(jiàn)。