React初接觸
最初接觸的前端框架是angular 1.x饼疙,對于當時習慣jquery寫頁面模式的我來說沖擊還是很大的拷窜。前端也可以用后端的方式使用mvc的模式來構建丽旅,而且前端項目也能夠處理著么復雜的邏輯(以前寫php的時候染服,項目的邏輯主要是放在后端做的豪椿,通過Controller來渲染不同的頁面奔坟,前端反反而只是單純對數(shù)據(jù)的渲染)。當時對我沖擊最大的反而不是mvc這樣大的模式搭盾,而是數(shù)據(jù)綁定真是太好用了咳秉。
相信之前基于jquery方式實現(xiàn)前端頁面的時候,都會因為繁瑣的dom操作而感到煩惱鸯隅,因此當時angular的數(shù)據(jù)綁定大大減輕了開發(fā)的壓力澜建,項目構建也變的簡單很多。當然蝌以,angular也存在很多問題炕舵,個人感覺angular的思想很大一部分來自后端開發(fā),這對于前端工程師可能天然有些不太友好跟畅,另一方面就是被吐槽很多的幕侠,angular這個框架太重了。不僅僅是代碼的大小碍彭,對于一些簡單的項目晤硕,我們不需要angular這么復雜的框架,這點特別是體現(xiàn)在移動端上庇忌。
因此舞箍,當時Vue的出現(xiàn)也給我?guī)砹撕艽篌@喜。Vue框架很輕皆疹,一個簡單的MVVM框架疏橄,只是將View層和Modal層進行了綁定。而且Vue還保留了很多Dom的細節(jié),對于前端工程師來說可以迅速的轉換捎迫。實習期間一直使用Vue在移動端進行開發(fā)晃酒,在移動端表現(xiàn)也十分友好,配合其它一些庫可以迅速的搭建一個公眾號應用窄绒。
真正接觸React是工作后贝次,之前對于React其實一直是一個比較抵抗的狀態(tài),一個是jsx的寫法當時不太適應彰导,另一個方面是感覺React能做的Vue也能夠實現(xiàn)蛔翅,因此對于React的理解都不太深。
使用React開發(fā)
真正工作后位谋,第一個任務便是對之前的一個React項目進行維護和改造山析。當時React給我的第一個感覺就是混亂!由于歷史原因掏父,項目立項的要求是比較快的能夠上線笋轨,因此沒有很好的預估,并且沒有引入redux赊淑。因此整個代碼中你就能看到無數(shù)的回調爵政,以及constructor中state里的一大串參數(shù)。
習慣Vue開發(fā)模式后膏燃,這個項目的代碼給我的感覺就是亂茂卦,一方面因為它本身比較復雜何什,另一方面Vue作為一個Vm框架组哩,它有一個model層來對組件的數(shù)據(jù)做一個管理,而React作為一個單純的View層框架处渣,每一個組件只能通過state來維持自己的狀態(tài)伶贰。因此當遇到組件間需要進行通信的時候,react簡直是災難性的罐栈。項目中我見過大量的函數(shù)在父組件中進行綁定黍衙,然后傳給子組件進行調用,有些函數(shù)甚至向下傳導了數(shù)層荠诬,同時琅翻,由于狀態(tài)都是組件自己維護的,因此在一個組件中你也能見到大量的setState的方法柑贞。這樣在后期維護的過程中是十分不方便的方椎,有時候需要加功能的時候,我需要從一個組件一層一層的跳到多個組件中來尋找一些方法的調用钧嘶。
當然棠众,react肯定也有自己的好處,作為一個單純的View層,react組件間的耦合度更低了闸拿,也是第一次的體會到了“搭積木”的開發(fā)模式空盼。react的生態(tài)圈十分完善,除去各個公司自己的組件庫新荤,很多開源的組件庫也是十分優(yōu)秀的揽趾,比如google的material-UI以及螞蟻金服的ant-design。使用react可以迅速的搭建一個前端項目迟隅。
Redux的引入
之前也說過沒有Redux的react項目是十分混亂的但骨,因此Redux的引入給React項目帶了很大的改觀。Redux的使用過程中智袭,我感覺它給我?guī)硪韵潞锰?
- 有一個統(tǒng)一的地方來管理我們的數(shù)據(jù)
- 組件間的通信變的更加簡單
Redux像一個全局的Model奔缠,管理項目中的數(shù)據(jù),這樣的話我們在設計組件的時候我們可以將UI邏輯和數(shù)據(jù)邏輯做更深的抽離吼野。在我開發(fā)的過程中校哎,對于一些組件的實現(xiàn)我會盡可能的將它設計成一個stateless(無狀態(tài))并且可以pure render的組件。通俗來說就是在設計組件的時候會把它想象成一個盒子瞳步,它不包含任何業(yè)務邏輯闷哆,它的作用就是把我給它的數(shù)據(jù)進行渲染,它的表現(xiàn)形式也完全由傳遞過來的屬性決定单起。這個思想跟函數(shù)的解耦以及Spring中的切片模式是同樣的概念抱怔,組件相當于一個函數(shù),這個函數(shù)中不依賴其它對象也不保存狀態(tài)嘀倒。
另一個方面是組件間的通信屈留,Redux是一個單向的數(shù)據(jù)流:
組件觸發(fā)動作-->動作修改Redux中數(shù)據(jù)-->綁定Redux中數(shù)據(jù)的組件重新渲染
Redux(store)就像是一個全局的Model,組件View和Model進行綁定测蘑,當Model被修改后灌危,綁定的組件也會進行重新渲染。這樣的設計模式下碳胳,我們可以將組件間的通信邏輯抽象到數(shù)據(jù)層勇蝙。沒有Redux的開發(fā)模式下,當組件觸發(fā)一個動作挨约,比如Button的點擊味混,我需要考慮到這次點擊有沒有其它組件在監(jiān)聽,父組件需不需要對這個動作反饋诫惭,如果其它組件或者父組件也關心這個動作的話翁锡,我需要在這些組件中構建監(jiān)聽方法,bind后傳到觸發(fā)動作的組件中供調用贝攒。從上面的描述可以看到這樣的方式很繁瑣盗誊,當邏輯復雜后,就會出現(xiàn)大量的bind函數(shù)。引入Redux后哈踱,各個組件只需要綁定自己關注的數(shù)據(jù)荒适,組件的動作也可以抽像成對數(shù)據(jù)的修改,當數(shù)據(jù)修改后开镣,綁定這個數(shù)據(jù)的組件也會重新渲染刀诬。
Redux帶來的一些問題
Redux給我的開發(fā)帶來了很大遍歷,React構建前端應用也不會出現(xiàn)像之前提到的混亂問題邪财。但是它同樣給我?guī)砹艘恍馈?/p>
組件的過度設計
之前說過陕壹,在對組件進行設計的時候,我回盡可能的將組件的UI邏輯和數(shù)據(jù)邏輯(或者說項目邏輯)進行抽離树埠,因此在寫代碼的時候常常會出現(xiàn)這樣的問題糠馆,寫著寫著,突然覺得這段代碼需不需要抽離出來成一個單獨的組件呢怎憋?這時候我會花大量的時間去把這個組件的邏輯抽離出來又碌,把它想象成一個公共組件,同時考慮各種擴展性绊袋,這樣花費大量時間最后發(fā)現(xiàn)這個組件其實只在一個頁面被引用了毕匀。
其實很多時候這樣的事情是不需要的,特別是我們引入了如material或者ant這樣的前端組件庫的時候癌别。將組件細力度化皂岔,構造更多的通用組件這種工作可能更多的是在實現(xiàn)組件庫的時候考慮的,現(xiàn)實的工作中我更多是類似 “搭積木”的工作展姐,其實并沒有必要去實現(xiàn)很多的通用組件≡甓猓現(xiàn)在我在構建前端項目的時候通常會有一個View文件夾和一個Components文件夾,View里面通常放的是頁面組件诞仓,Components里面也只有少部分是通用組件缤苫,大部分是因為本身邏輯很復雜而被抽離出來的組件速兔。
.
├── common // Utils方法和一些靜態(tài)變量
├── components // 通用組件和一些項目組件
├── main.js //入口函數(shù)
├── redux // reducers 和 actions
└── views // 頁面組件
是否保留State
剛接觸Redux的時候墅拭,發(fā)現(xiàn)有很多的觀點認為應用Redux后,需要盡量的去避免State的使用涣狗,這樣的目的是盡量將組件抽離成stateless的UI組件谍婉,想法其實跟上面挺相似的。在我的實際開發(fā)中感覺這樣的想法可能有點極端镀钓,一些不需要共享的狀態(tài)完全可以交個組件自己去管理穗熬,Redux管理太多的數(shù)據(jù)也會使得項目邏輯變的太復雜。
組件從哪獲取屬性
在使用Redux后丁溅,我常常比較糾結唤蔗,組件的屬性從哪獲得?是從父組件中傳遞過來呢還是直接map到redux上呢?如果是都是從父組件上繼承的話妓柜,那么一些封裝到很深的組件就需要多次傳遞箱季,這樣對于后期開發(fā)和debug都會帶來額外的開銷。如果直接從redux的store中獲取棍掐,組件和redux連接太緊密了藏雏,有時候邏輯沒寫對,組件渲染不符合預期但是很難定位到問題在哪∽骰停現(xiàn)在對于大部分邏輯比較復雜的組件我都是直接從store中獲取數(shù)掘殴,一些簡單的組件則是從父親組件那里獲取。這都需要根據(jù)項目做具體調整粟誓,但是在一些場景中奏寨,可能需要直接從父組件中獲取,一個是可能需要做后端渲染直出的時候鹰服,另一個是組件可能需要跨平臺的時候服爷。
Redux相關優(yōu)化
在使用Redux的過程中,也有很多地方并沒有考慮清楚获诈,其實發(fā)現(xiàn)還是很多地方可以優(yōu)化仍源。
MapDispatchToProps
在實際開發(fā)過程中,redux我使用的是react-redux這個庫舔涎,里面的connect函數(shù)有兩個參數(shù)笼踩,一個是MapStateToProps,另一個是MapDispatchToProps亡嫌,MapDispatchToProps這個參數(shù)嚎于,這個參數(shù)的作用是將一些dispatch合并成function,然后作為Props中的屬性傳給組件挟冠。在很長一段時間里于购,我都沒有使用這個參數(shù),因為我感覺直接在組件中調用dispatch(action(payload))足夠解決問題知染。但是當項目變的復雜肋僧,同時需要別人來維護的時候,問題就體現(xiàn)出來了控淡。
比如一個很常用的例子嫌吠,當一個Button點擊后,我們希望改變它當前的disable狀態(tài)掺炭,在使用redux的方式下我通常會這樣實現(xiàn)功能:
// 組件監(jiān)聽
<Button onClick={this.handleClick} disable={this.props.currentStatus}/>
// 監(jiān)聽的回調
handleClick() { this.props.dispatch(CHANGESTATUS(XX))}
這樣看起來并沒什么問題辫诅,但是當需要dispatch多個action后事件就會變的比較復雜了。
// 組件監(jiān)聽
<Button
onClick={this.handleClick}
disable={this.props.currentStatus}>
{`${this.props.content}-${this.props.user}`}
</Button>
// 監(jiān)聽的回調
handleClick() {
this.props.dispatch(CHANGESTATUS(XX))涧狮;
this.props.dispatch(CHANGECONTENT(XX))炕矮;
this.props.dispatch(CHANGEUSER(XX))么夫;
}
這樣的話一個點擊動作會dispatch多個actions,同時這些actions都是直接對數(shù)據(jù)的修改肤视,從代碼看很難準確的確定這些actions是干嘛用的魏割。因此當我把這樣的代碼交給另外的維護者的時候,他可能需要去看看這些action都做了什么事情然后確定這次點擊做了什么钢颂。
這些dispatch action可能更像是對數(shù)據(jù)庫的增刪改查钞它,對于熟悉后端開發(fā)來說,他們很有可能會在上面重新進行一層封裝殊鞭,類似于dao層上的biz業(yè)務層遭垛。對于復雜的react項目同樣需要這樣一層封裝。MapDispatchToProps就是進行這樣的工作操灿。
// 對dispatch actions進行封裝
changeButtonContent(){
this.props.dispatch(CHANGESTATUS(XX))锯仪;
this.props.dispatch(CHANGECONTENT(XX));
this.props.dispatch(CHANGEUSER(XX))趾盐;
}
// 組件監(jiān)聽
<Button
onClick={this.handleClick}
disable={this.props.currentStatus}>
{`${this.props.content}-${this.props.user}`}
</Button>
// 監(jiān)聽的回調
handleClick() {
this.changeButtonContent();
}
這樣的話代碼邏輯就要清晰很多庶喜,而且很多時候我們會在一個地方對changeButtonContent這樣的函數(shù)進行一個統(tǒng)一的維護,而不是在各個組件中進行申明救鲤,這樣配合豐富的注釋久窟,代碼的邏輯將更加清晰。更加深入的話我們可以在這些統(tǒng)一維護的函數(shù)之上接著封裝一層Controller層本缠,事件的觸發(fā)將被這個Controller handle斥扛,來決定調用哪個函數(shù)來處理事件。當然這樣就變成類似于angular那種MVC框架了丹锹,是否要用還是需要根據(jù)項目的具體場景來做稀颁。
immutable.js
redux的要求reducer每次處理action都需要返回一個新的state。在開發(fā)過程中楣黍,如果沒有immutable.js 的話匾灶,通常是實用Object.assign來實現(xiàn),但是Object.assign無法對一些數(shù)據(jù)進行深拷貝租漂,比如一個Object里的array阶女,而react屬性里面的數(shù)組卻是傳引用。因此開發(fā)的時候經常需要注意對這個array進行slice(0)操作窜锯,以獲得一個不同引用的array张肾。
immutable對js中的一些常用數(shù)據(jù)結構進行了封裝芭析,同時提供了新的api接口锚扎,每次調用這些接口都將是一個新的對象,從而避免了上面的問題馁启。只是為了避免深拷貝帶來的問題驾孔,loadsh類似的庫就能實現(xiàn)芍秆,為什么需要使用immutable,還需要重新學習一套新的api翠勉?immutable帶了更多的可能是后期對react項目的優(yōu)化妖啥,配合shouldComponentUpdate可以減少很多多余的渲染。這方面沒有做過相關實踐对碌,不過這篇文章寫的挺好荆虱。
最后
現(xiàn)在前端發(fā)展越來越快了,而且更加多的向跨平臺發(fā)展了朽们,從最初的react native,以及現(xiàn)在的weex怀读,還有很多需要學習的。
單身狗的十一??骑脱,做些紀錄菜枷,加油共勉。