本文主要談自己在react學(xué)習(xí)的過程中總結(jié)出來的一些經(jīng)驗(yàn)和資源枉氮,內(nèi)容邏輯參考了“深入react技術(shù)椗鑫常”一書以及網(wǎng)上的諸多資源,但也并非完全照抄,代碼基本都是自己實(shí)踐,主要為平時(shí)個(gè)人學(xué)習(xí)做一個(gè)總結(jié)和參考乡数。
本文的關(guān)鍵內(nèi)容:樣式處理與css模塊化、組件間通信(非flux架構(gòu))读存、組件抽象、組件性能優(yōu)化以及React 動(dòng)畫五種內(nèi)容。
1.樣式處理與css模塊化
在react出現(xiàn)之前,我們寫樣式一般是將css分離的榆俺,并且用less/sass預(yù)處理器,我個(gè)人在用backbone等MV*框架的時(shí)候就習(xí)慣用less并且用nodejs配置一個(gè)模塊用來編譯less。
但這樣寫會(huì)有一些問題:
- 命名沖突是一個(gè)很常見的問題谴仙,因此迂求,我們要制定出一套自己的完整命名規(guī)范來,并且要防止和項(xiàng)目中引入的庫出現(xiàn)沖突晃跺。
- 充分利用優(yōu)先級(jí)是一個(gè)比較好的實(shí)踐,但是這樣寫出的less代碼有點(diǎn)像回調(diào)函數(shù)塔毫玖,雖然我本人并不覺得這有什么不好甚至還比較享受這種編程掀虎,但這的確不利于充分壓縮css代碼。
于是我們引入css modules付枫。
簡單的說烹玉,如果我們配置了css modules的話,那么你在css中寫的類名和你在組件中寫的class = ...
都會(huì)被重新編譯成一個(gè)哈希字符串阐滩,這樣我們就不用考慮命名沖突的問題了二打,另外也可以比較自由的在local和global的css變量之間切換(實(shí)際上,這樣的css變量默認(rèn)都是local的掂榔,如果需要global继效,我們需要:global
前綴,這樣的話css變量就不會(huì)被轉(zhuǎn)化成特殊的哈希值了)
需要注意的是寫法問題装获,這個(gè)時(shí)候我們就不能在jsx中僅僅用className了瑞信,css module實(shí)際上限制了我們必須要用className={style.title}
這樣的寫法,實(shí)際上我在嘗試的時(shí)候因?yàn)檫@個(gè)地方的bug調(diào)試了很久穴豫,而這也在某一種程度上給利用css module進(jìn)行重構(gòu)代碼帶來了一些困難凡简。
關(guān)于css modules的入門介紹,沒錯(cuò)精肃,阮一峰老師寫了一份:http://www.ruanyifeng.com/blog/2016/06/css_modules.html
另外秤涩,有的同學(xué)認(rèn)為css modules并不夠優(yōu)雅,實(shí)際上上文的寫法限定的問題就是一個(gè)麻煩事司抱,所以我們可以用react-css-modules庫筐眷,這個(gè)庫解決了css modules的一些不是很好的問題,因?yàn)樯鲜植⒉浑y状植,這里不詳細(xì)介紹了(可以參考這里以及“深入react技術(shù)椬蔷梗”73頁)
2.組件間通信(非flux架構(gòu))
接下來我們總結(jié)一下react組件間通信的幾種方式,雖然現(xiàn)在有了redux等最佳實(shí)踐津畸,但是很多時(shí)候我們還是需要原生可用的組件通信機(jī)制振定。
父組件向子組件之間
非常常見,通過props機(jī)制傳遞即可肉拓。
子組件向父組件通信
- 利用回調(diào)函數(shù)后频,回調(diào)函數(shù)本身定義在父組件中,通過props方式傳遞給子組件,在子組件中調(diào)用回調(diào)函數(shù)卑惜。
- 利用自定義事件機(jī)制膏执,這種方法更通用方便,并且可以簡化API露久,關(guān)于自定義事件機(jī)制的詳細(xì)使用方法我們在接下來展開更米。
跨級(jí)組件通信
- context機(jī)制。不過這種機(jī)制react并不是特別推薦(不是特別推薦并不代表會(huì)在將來的版本沒有毫痕,只是說明可能會(huì)產(chǎn)生一定的弊端因此要慎用少用)征峦,context機(jī)制需要在上級(jí)組件(可以是父組件的父組件)定義一個(gè)getChildContext函數(shù)如下:
getChildContext(){
return{
color:"red",
}
}
- 當(dāng)然也可以用事件機(jī)制
沒有層級(jí)關(guān)系的組件通信
這回只能用事件機(jī)制了,雖然我之前分析過別的框架的事件機(jī)制部分都可以單獨(dú)拎出來用消请,但是這里面實(shí)際上有好多方式栏笆。
我首先試了一下js-signals這個(gè)庫,這個(gè)也是React團(tuán)隊(duì)使用的臊泰,用起來也還簡單蛉加,npm install signals
之后,我們可以單獨(dú)寫一個(gè)Signal文件:
const signals= require('signals');
var Signal = {
started : new signals.Signal()
};
我們可以把接收事件的函數(shù)定義在組件B中:
onStarted(param1, param2){
alert(param1 + param2);
}
constructor(props){
super(props);
Signal.started.add(this.onStarted); //add listener
}
然后在組件A中(注意dispatch的時(shí)候要保證B已經(jīng)被構(gòu)造出來了):
handlethis () {
Signal.started.dispatch('foo', 'bar'); //dispatch signal passing custom parameters
}
render(){
return (
<button onClick={this.handlethis}>發(fā)射事件</button>
)
}
其實(shí)還有很多類似的組件缸逃,當(dāng)然我們自己寫一個(gè)功能弱的也不成問題针饥,更多的方式,這篇文章介紹的不錯(cuò)察滑。
3.組件抽象
mixin
mixin是一個(gè)飽受詬病的東西打厘,另外蛋疼的是在ES6的寫法下也不能用,筆者現(xiàn)在寫react的時(shí)候都已經(jīng)不用了贺辰,所以這里進(jìn)行簡單介紹户盯。
我們可以通過在createClass的時(shí)候傳入一個(gè)mixins數(shù)組,這個(gè)數(shù)組里是我們的一些通用的方法:
React.createClass({
mixins:[method1,method2]
//...
})
這在ES6的class形式下是不能“直接”使用的饲化。
ES7 decorator 與 mixin
ES7 的 decorator莽鸭,作用就是返回一個(gè)新的 descriptor,并把這個(gè)新返回的 descriptor 應(yīng)用到目標(biāo)方法上吃靠。稍后我們將會(huì)看到硫眨,decorator 并非只能作用到類的方法/屬性上,它還可以作用到類本身巢块。
當(dāng)然礁阁,這個(gè)我只言片語肯定說不明白的,這個(gè)我要推薦淘寶前端團(tuán)隊(duì)的這篇文章族奢。
另外姥闭,core-decorators這個(gè)庫值得關(guān)注,它里面有一個(gè)mixin方法用于實(shí)現(xiàn)mixin越走,原理就是用了ES7 decorator棚品,實(shí)現(xiàn)起來也不是非常復(fù)雜靠欢。
最后,提醒一下ES7 decorator雖然很酷铜跑,但是目前還處于提案階段门怪,雖然借助babel我們已經(jīng)可以體驗(yàn)了,但是距離真正支持還有一段距離
高階組件(HOC)
這是一個(gè)頗值得一提的話題锅纺。
我們應(yīng)該聽說過高階函數(shù)掷空,這種函數(shù)接受函數(shù)作為輸入,或者是輸出一個(gè)函數(shù)囤锉,比如map拣帽、reduce以及sort等函數(shù)。
一個(gè)高階組件只是一個(gè)包裝了另外一個(gè) React 組件的 React 組件嚼锄, 這種包裝通常有兩種方式:
1、屬性代理(Props Proxy):高階組件操控傳遞給 WrappedComponent 的 props蔽豺,
2区丑、反向繼承(Inheritance Inversion):高階組件繼承(extends)WrappedComponent。
高階組件的功能主要有以下幾點(diǎn):
1修陡、代碼復(fù)用沧侥,邏輯抽象,抽離底層準(zhǔn)備(bootstrap)代碼
2魄鸦、渲染劫持
- 渲染劫持主要通過反向繼承來實(shí)現(xiàn)宴杀,我們可以選擇是否渲染原組件,也可以改變原組件的渲染結(jié)果(注意:我們通過
var elementsTree = super.render()
可以拿到原組件的渲染結(jié)果拾因,然后我們可以改變props之后旺罢,通過原生cloneElement方法創(chuàng)建出新的節(jié)點(diǎn)樹)
3、State 抽象和更改
- 所謂抽象state的目的绢记,就是將原組件作為一個(gè)純粹的展示型組件扁达,分離內(nèi)部狀態(tài),將state交給高階組件來控制蠢熄。比如:我們可以抽象出一個(gè)控制input的高階組件跪解,從而不用在input中來有很多控制state的代碼。
4签孔、Props 更改
- 我們可以讀取叉讥、增加、編輯饥追、刪除被包裹組件的props
我在這里沒有給出代碼图仓,為了避免文章過于冗長以及和網(wǎng)上其他專題文章大部分重復(fù),我主要是進(jìn)行一些總結(jié)判耕,具體內(nèi)容我這里仍然是推薦一篇文章
4.組件性能優(yōu)化
PureRender
PureRender這個(gè)概念實(shí)際上和純函數(shù)有關(guān)透绩,Pure指的是對(duì)同樣的輸入(對(duì)于react來說就是props和state)總是得到相同的輸出,針對(duì)這個(gè)問題,React有一個(gè)shouldComponentUpdate
鉤子帚豪,這個(gè)鉤子默認(rèn)返回true碳竟,用于props或者state改變或者接收到新的值時(shí)候,可以供用戶重寫狸臣,這樣在接受到相同的props的時(shí)候我們就可以防止其重新渲染莹桅。
PureRenderMixin在這個(gè)時(shí)候要派上用場了,這是一個(gè)能夠?qū)崿F(xiàn)上述功能的官方插件烛亦,react是這樣介紹它的:
If your React component's render function renders the same result given the same props and state, you can use this mixin for a performance boost in some cases.
實(shí)際上是通過一個(gè)淺比較來確定是不是該被渲染诈泼,這實(shí)際上是一個(gè)性能上的權(quán)衡和妥協(xié),深比較真的是耗費(fèi)太多(我們在下一節(jié)會(huì)提出一個(gè)更好的解決方案)煤禽。
寫法也比較簡單:
import PureRenderMixin from 'react-addons-pure-render-mixin';
class FooComponent extends React.Component {
constructor(props) {
super(props);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
}
render() {
return <div className={this.props.className}>foo</div>;
}
}
我們可以在這里查看更多信息铐达。
Immutable.js
在傳遞數(shù)據(jù)的時(shí)候,我們可以用Immutable Data進(jìn)一步提高性能檬果。
Immutable.js定義了不可變對(duì)象瓮孙,一個(gè)數(shù)據(jù)結(jié)構(gòu)(Map\List\ArraySet)一旦被定義,就不可變了选脊,我們把它如果用于state杭抠,那么每次變化的時(shí)候需要將state整個(gè)重新賦值。
上文提到恳啥,在shouldComponentUpdate使用PureRenderMixin由于性能權(quán)衡我們只能使用淺比較偏灿,但是如果我們用了Immutable.js,我們有更好的方式:直接用=== or is
就可以判斷钝的,因?yàn)镮mmutable.js比較的是兩個(gè)對(duì)象的hashCode或者valueOf翁垂,并且內(nèi)部使用了trie數(shù)據(jù)結(jié)構(gòu)(比如字典樹)來存儲(chǔ),因此性能很高扁藕。
另外沮峡,由于Immutable.js中提供的數(shù)據(jù)結(jié)構(gòu)是不可變的,我們不用擔(dān)心js中源對(duì)象跟隨引用對(duì)象的變化而變化的問題亿柑,也不用考慮函數(shù)中所謂的引用賦值邢疙,這給我們的編程帶來了很多方便。
當(dāng)然也有不方便的是Immutable.js的數(shù)據(jù)結(jié)構(gòu)并不能和原生的數(shù)據(jù)結(jié)構(gòu)混用望薄,因此寫法上需要格外注意疟游,關(guān)于更多資料請看這里.
無狀態(tài)組件
生命周期讓react的組件變得功能非常強(qiáng)大并且復(fù)雜,從而難以維護(hù)痕支,而有的時(shí)候我們又要經(jīng)常寫一些自身沒有狀態(tài)颁虐,只從父組件接受props的組件,這種組件可以提高react的渲染性能卧须,也被官方推薦另绩。
const HelloWorld = (props) => <div>{props.name}</div>
ReactDOM.render(<HelloWorld name="HelloWorld" />,App)
簡單儒陨,高效,在有些不需要改變的地方笋籽,比如沒有用戶交互純聲明性質(zhì)的內(nèi)容蹦漠,可以用無狀態(tài)組件。
react的diff算法
我們想要讓效率更高车海,還要注意的一點(diǎn)就是要照顧react的diff算法笛园,react雖然有一個(gè)復(fù)雜度僅為O(N)的diff算法,但是這個(gè)算法也不是萬能的侍芝,我們要想讓react效能最大化研铆,就要去照顧這個(gè)diff算法。
總的說來州叠,這個(gè)diff算法大概有三點(diǎn)實(shí)現(xiàn)概要:
- 對(duì)兩棵樹進(jìn)行比較棵红,react認(rèn)為,對(duì)節(jié)點(diǎn)的跨層級(jí)操作移動(dòng)較少咧栗,所以只會(huì)對(duì)相同層級(jí)的dom節(jié)點(diǎn)進(jìn)行比較窄赋,即同一個(gè)父節(jié)點(diǎn)下的字節(jié)點(diǎn),當(dāng)發(fā)現(xiàn)節(jié)點(diǎn)已經(jīng)不存在時(shí)楼熄,就會(huì)刪除節(jié)點(diǎn),當(dāng)發(fā)現(xiàn)節(jié)點(diǎn)新增時(shí)候浩峡,就會(huì)插入節(jié)點(diǎn)可岂。
- 為了迎合這個(gè)策略,我們盡量不要對(duì)dom節(jié)點(diǎn)進(jìn)行跨層級(jí)操作(比如把某一個(gè)字節(jié)點(diǎn)轉(zhuǎn)而掛在到某一個(gè)孫節(jié)點(diǎn)下面)翰灾,因?yàn)檫@樣效率是比較低的缕粹。
- 對(duì)組件之間進(jìn)行比較:如果是同一個(gè)類型的組件,按照第一條策略進(jìn)一步比較虛擬dom樹纸淮;如果不是平斩,就將該組件判斷為dirty,從而替換所有字節(jié)點(diǎn)咽块;對(duì)于同一類型的組件绘面,有可能其虛擬dom樹沒有發(fā)生變化,如果能夠確切知道這一點(diǎn)侈沪,那么就可以節(jié)省大量diff的操作時(shí)間揭璃,因此,react允許用戶通過shouldComponentUpdate鉤子來判斷組件是否發(fā)生變化亭罪。
- 為了迎合這個(gè)策略瘦馍,我們可以使用上面提到的PureRender或者Immutable.js。
- 當(dāng)節(jié)點(diǎn)處于同一個(gè)層級(jí)应役,react提供了插入情组、移動(dòng)燥筷、刪除操作,這里主要指相似節(jié)點(diǎn)院崇,比如<li>標(biāo)簽肆氓,因此react允許開發(fā)者將同一個(gè)層級(jí)的節(jié)點(diǎn)添加唯一key進(jìn)行操作,同一個(gè)key認(rèn)為是相同節(jié)點(diǎn)亚脆。之后react有一套自己的算法規(guī)則做院,對(duì)節(jié)點(diǎn)進(jìn)行移動(dòng)操作以達(dá)到要求(具體可以參考“深入react技術(shù)棧”176頁)濒持。
- 為了迎合這一規(guī)則键耕,我們要給li標(biāo)簽等添加一個(gè)key(實(shí)際上已經(jīng)被react強(qiáng)制),另外,在開發(fā)過程中盡量減少將最后一個(gè)節(jié)點(diǎn)移動(dòng)到第一個(gè)的情況柑营,因?yàn)檫@個(gè)時(shí)候react要進(jìn)行很多的移動(dòng)操作屈雄。
5.React 動(dòng)畫
緩動(dòng)函數(shù)
對(duì)于各種動(dòng)畫來說,緩動(dòng)體驗(yàn)一般是:linear < ease淡入淡出 < spring彈性動(dòng)畫\cubic bezier貝塞爾曲線
官套。
動(dòng)畫的方式有css動(dòng)畫和js動(dòng)畫酒奶,但是很多時(shí)候我們都是一起用的,所以區(qū)分的太詳細(xì)似乎必要性也不大奶赔。
成熟的動(dòng)畫庫
實(shí)際上動(dòng)畫經(jīng)常是筆者比較忽視的一個(gè)方面惋嚎,由于還沒畢業(yè),大多時(shí)候都是自己做小東西站刑,最后動(dòng)畫就成了可有可無的環(huán)節(jié)另伍,另外現(xiàn)在的各種動(dòng)畫庫很多,方便到只需要一個(gè)class绞旅、只寫一行代碼就可以做出相對(duì)過得去的效果摆尝,自己也就疏于探索。
這部分內(nèi)容主要推薦一些成熟的動(dòng)畫庫因悲。
首先是ReactCSSTransitionGroup堕汞,這個(gè)動(dòng)畫庫提供了一些生命周期鉤子,我們可以利用此加動(dòng)畫晃琳,具體學(xué)API的過程相當(dāng)簡單讯检,我相信看懂上面各個(gè)部分的同學(xué)直接按照給出的鏈接肯定能順利學(xué)會(huì)。
還有react-smooth動(dòng)畫庫卫旱,這也是一個(gè)比較有意思的動(dòng)畫庫视哑,寫法類似css的多關(guān)鍵幀動(dòng)畫。并且?guī)追N緩動(dòng)函數(shù)動(dòng)畫這里都能實(shí)現(xiàn)誊涯。
react-motion也是一個(gè)值得推薦的動(dòng)畫庫挡毅,如果想用spring動(dòng)畫這個(gè)似乎是更好的選擇。
另外暴构,不說react跪呈,還有一個(gè)讓我印象深刻不得不提的就是vivus.js這個(gè)svg動(dòng)畫庫段磨,不得不說真是酷斃了。