最近在學(xué)習(xí)React扛拨,并使用React做了一個(gè)cnode(歡迎大家給我star使套、issue美尸,一起學(xué)習(xí)討論進(jìn)步),現(xiàn)就記錄一下自己的React學(xué)習(xí)筆記骄瓣。
學(xué)習(xí)資料
環(huán)境配置
React的環(huán)境配置很麻煩停巷,剛上手時(shí)可以使用React腳手架來(lái)進(jìn)行學(xué)習(xí)。推薦Create React App榕栏,或者可以使用我自己寫(xiě)的一個(gè)腳手架畔勤。
Jsx語(yǔ)法
簡(jiǎn)單來(lái)說(shuō),就是可以把html和js混在一起寫(xiě)扒磁,即html代碼里面可以有js代碼庆揪,js代碼里面可以有html。需要注意的是妨托,在html中遇到j(luò)s代碼缸榛,需要加花括號(hào),而在js代碼中遇到html代碼兰伤,需要加圓括號(hào)内颗。
創(chuàng)建組件
創(chuàng)建組件有兩種方式:
- 函數(shù)創(chuàng)建
function Hello() {
return <h1>Hello World</h1>;
}
- ES6的class
class Hello extends React.Component {
render() {
return <h1>Hello World</h1>;
}
}
不同之處:函數(shù)式組件沒(méi)有state,也不能使用生命周期函數(shù)敦腔。
注意:組件名稱必須以大寫(xiě)字母開(kāi)頭均澳。組件的返回值只能有一個(gè)根元素。
props與state
- props
props是只讀的符衔,組件絕對(duì)不能修改自己的props - state
狀態(tài)與屬性十分相似负懦,但是狀態(tài)是私有的,完全受控于當(dāng)前組件柏腻。
更新?tīng)顟B(tài)只有一個(gè)辦法:那就是調(diào)用this.setState()
該方法有兩種調(diào)用方式// 接受一個(gè)對(duì)象為參數(shù) this.setState({ loading: false }) // 接受一個(gè)函數(shù)為參數(shù),函數(shù)的第一個(gè)參數(shù)為先前的狀態(tài),第二個(gè)參數(shù)為props this.setState((prevState, props) => ({ loading: !prevState.loading })) // 除此之外纸厉,this.setState()還接受一個(gè)可選的回調(diào)函數(shù)作為第二個(gè)參數(shù)
條件加載
React的條件加載和JavaScript中的條件判斷一樣,我們可以使用條件運(yùn)算符(?:)五嫂,與運(yùn)算符(&&)等來(lái)進(jìn)行條件加載颗品。
循環(huán)加載
我們一般按如下的方式進(jìn)行循環(huán)加載
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
)
//一個(gè)元素的key最好是這個(gè)元素在列表中擁有的一個(gè)獨(dú)一無(wú)二的字符串肯尺。
//通常,我們使用來(lái)自數(shù)據(jù)的id作為元素的key躯枢。當(dāng)元素沒(méi)有確定的id時(shí)则吟,你可以使用他的序列號(hào)索引index作為key
操作表單
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
return (
<div>
<label>Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
</div>
);
}
}
一個(gè)簡(jiǎn)單操作表單元素的例子就是這樣。但是我們會(huì)發(fā)現(xiàn)這樣操作表單有一些小問(wèn)題锄蹂。假如該表單有很多表單元素氓仲,那么我們需要為每一個(gè)表單元素注冊(cè)一個(gè)change事件的處理函數(shù),這會(huì)讓組件顯得很臃腫得糜。不過(guò)不用擔(dān)心敬扛,遇到這種情況我們依然有解決辦法。那就是使用ref朝抖。
關(guān)于Ref
我們可以給DOM元素啥箭,類組件添加ref屬性,不能給函數(shù)式組件添加ref屬性治宣。
ref 屬性接受一個(gè)回調(diào)函數(shù)急侥,它在組件被加載或卸載時(shí)會(huì)立即執(zhí)行。
ref屬性也接受一個(gè)字符串侮邀,(不過(guò)未來(lái)可能會(huì)廢棄坏怪,推薦使用回調(diào))
ref 在加載時(shí)回調(diào)接收了底層的DOM元素或已經(jīng)加載的 React 實(shí)例作為參數(shù),在卸載時(shí)則會(huì)傳入 null绊茧。
class NameForm extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<label>Name:
<input type="text" ref={ input => this.input=input }/>
</label>
</div>
);
}
}
// 此時(shí)this.input指向inputDOM元素陕悬,我們可以使用this.input.value得到用戶的輸入。
除可以操作DOM之外按傅,ref還用于觸發(fā)強(qiáng)制動(dòng)畫(huà)捉超,處理焦點(diǎn)、文本選擇或媒體控制等唯绍。
不過(guò)我們應(yīng)該盡量避免使用ref拼岳。
事件處理
- React事件綁定屬性的命名采用駝峰式寫(xiě)法。
- 如果采用 JSX 的語(yǔ)法需要傳入一個(gè)函數(shù)作為事件處理函數(shù)况芒,而不是一個(gè)字符串惜纸。
- 不能使用返回 false 的方式阻止默認(rèn)行為。
- 事件對(duì)象是一個(gè)合成對(duì)象绝骚,所以不需要擔(dān)心跨瀏覽器的兼容性問(wèn)題耐版。
- 事件處理程序會(huì)成為類的一個(gè)方法(類的方法默認(rèn)是不會(huì)綁定this 的),所以我們需要手動(dòng)綁定this压汪。
手動(dòng)綁定this有兩種方法粪牲,一是在類的構(gòu)造函數(shù)中使用bind綁定,二是在DOM中使用bind綁定止剖。
如果你的事件處理程序是箭頭函數(shù)腺阳,則不需要手動(dòng)綁定this落君。 - 向事件處理程序傳遞參數(shù)有兩種方法,一是使用箭頭函數(shù)亭引,二是使用bind方法绎速。
<button onClick={(e) => this.confirm(id, e)}>確定</button>
<button onClick={this.confirm.bind(this, id)}>確定</button>
- 事件處理程序的最后一個(gè)參數(shù)為事件對(duì)象(該事件對(duì)象是SyntheticEvent的實(shí)例)。
如果由于某些原因焙蚓,你得使用一些底層的瀏覽器事件纹冤,只需用nativeEvent的屬性就能得到原生的事件對(duì)象。
DOM屬性
React實(shí)現(xiàn)了一套與瀏覽器無(wú)關(guān)的DOM系統(tǒng)购公,兼顧了性能和跨瀏覽器的兼容性萌京。
在React中,所有的DOM特性和屬性都是小駝峰命名法命名君丁。(aria-和data-屬性除外)
一些不同之處:
- 使用className屬性指定一個(gè)CSS類。
- style屬性接受一個(gè)鍵為小駝峰命名法命名的javascript對(duì)象作為值将宪。(注意:樣式屬性不會(huì)自動(dòng)補(bǔ)齊前綴的绘闷,瀏覽器前綴除了ms以外,都應(yīng)該以大寫(xiě)字母開(kāi)頭)
- onChange函數(shù)较坛,無(wú)論form表單何時(shí)發(fā)生變化印蔗,這個(gè)事件都會(huì)被觸發(fā)。
- dangerouslySetInnerHTML函數(shù)是替換瀏覽器DOM中的innerHTML接口的一個(gè)函數(shù)丑勤。
生命周期
組件的生命周期大致分為三個(gè)階段
-
裝配階段(這些方法會(huì)在組件實(shí)例被創(chuàng)建和插入DOM中時(shí)被調(diào)用)
constructor()
componentWillMount()
render()
componentDidMount()
-
更新階段(屬性或狀態(tài)的改變會(huì)觸發(fā)一次更新华嘹。當(dāng)一個(gè)組件在被重渲時(shí),這些方法將會(huì)被調(diào)用)
componentWillReceiveProps(nextProps)
shouldComponentUpdate(nextProps, nextState)
componentWillUpdate(nextProps, nextState)
render()
componentDidUpdate(prevProps, prevState)
-
卸載階段(當(dāng)一個(gè)組件被從DOM中移除時(shí)法竞,該方法被調(diào)用)
componentWillUnmount()
除此之外耙厚,組件還有一個(gè)方法forceUpdate()
,調(diào)用forceUpdate()
將會(huì)導(dǎo)致組件的 render()
方法被調(diào)用岔霸,并忽略shouldComponentUpdate()
薛躬。
注意點(diǎn):
- 若
shouldComponentUpdate()
返回false,render()
函數(shù)將不會(huì)被調(diào)用呆细。 - 當(dāng)為一個(gè)React.Component子類定義構(gòu)造函數(shù)時(shí)型宝,你應(yīng)該在任何其他的表達(dá)式之前調(diào)用
super(props)
。否則絮爷,this.props
在構(gòu)造函數(shù)中將是未定義趴酣,并可能引發(fā)異常。 -
defaultProps
可以被定義為組件類的一個(gè)屬性坑夯,用以為類設(shè)置默認(rèn)的屬性岖寞。
常見(jiàn)問(wèn)題
- 使用React開(kāi)發(fā)必須使用JSX語(yǔ)法嗎?
答:JSX并不是必須的柜蜈。每一個(gè)JSX元素都只是React.createElement(component, props, ...children)
的語(yǔ)法糖慎璧。
因此床嫌,任何時(shí)候你用JSX語(yǔ)法寫(xiě)的代碼也可以用普通的 JavaScript 語(yǔ)法寫(xiě)出來(lái)。 - 使用React開(kāi)發(fā)必須使用ES6+語(yǔ)法嗎胸私?
答:ES6+并不是必須的厌处。
虛擬DOM
- 虛擬DOM具有batching(批處理)和高效的Diff算法。
- batching把所有的DOM操作搜集起來(lái)岁疼,一次性提交給真實(shí)的DOM阔涉。diff算法時(shí)間復(fù)雜度也從標(biāo)準(zhǔn)的的Diff算法的O(n^3)降到了O(n)。
- render執(zhí)行的結(jié)果得到的并不是真正的DOM節(jié)點(diǎn)捷绒,結(jié)果僅僅是輕量級(jí)的JavaScript對(duì)象瑰排,我們稱之為virtual DOM。
- 我們利用虛擬DOM樹(shù)去構(gòu)造真實(shí)DOM樹(shù)暖侨,然后插入到文檔中椭住,當(dāng)數(shù)據(jù)變化時(shí),生成一個(gè)新的虛擬DOM樹(shù)字逗,比較新的虛擬DOM樹(shù)與舊的虛擬DOM樹(shù)京郑,得到差異葫掉,將差異應(yīng)用到真實(shí)DOM中。
- Virtual DOM并沒(méi)有完全實(shí)現(xiàn)DOM俭厚,Virtual DOM最主要的還是保留了Element之間的層次關(guān)系和一些基本屬性。
React diff算法
- 樹(shù)對(duì)比
React 對(duì)樹(shù)的算法進(jìn)行了簡(jiǎn)潔明了的優(yōu)化挪挤,即對(duì)樹(shù)進(jìn)行分層比較,兩棵樹(shù)只會(huì)對(duì)同一層次的節(jié)點(diǎn)進(jìn)行比較扛门。 - 組件對(duì)比
- 如果是同一類型的組件幢码,按照原策略繼續(xù)比較 virtual DOM tree。
- 如果不是尖飞,直接刪除舊組件症副,然后在該位置創(chuàng)建新組件。
- 對(duì)于同一類型的組件政基,有可能其 Virtual DOM 沒(méi)有任何變化贞铣,如果能夠確切的知道這點(diǎn)那可以節(jié)省大量的 diff 運(yùn)算時(shí)間,因此 React 允許用戶通過(guò) shouldComponentUpdate() 來(lái)判斷該組件是否需要進(jìn)行 diff沮明。
- 元素對(duì)比
允許開(kāi)發(fā)者對(duì)同一層級(jí)的同組子節(jié)點(diǎn)辕坝,添加唯一 key 進(jìn)行區(qū)分,雖然只是小小的改動(dòng)荐健,性能上卻發(fā)生了翻天覆地的變化酱畅! - 總結(jié)
- React 通過(guò)分層求異的策略琳袄,對(duì) tree diff 進(jìn)行算法優(yōu)化;
- React 通過(guò)相同類生成相似樹(shù)形結(jié)構(gòu)纺酸,不同類生成不同樹(shù)形結(jié)構(gòu)的策略窖逗,對(duì) component diff 進(jìn)行算法優(yōu)化;
- React 通過(guò)設(shè)置唯一 key的策略餐蔬,對(duì) element diff 進(jìn)行算法優(yōu)化碎紊;
性能優(yōu)化
- 一般做法
- 使用工具來(lái)分析性能瓶頸。
- 嘗試使用優(yōu)化技巧解決這些問(wèn)題樊诺。
- 使用工具測(cè)試性能是否確實(shí)有提升仗考。
- 具體實(shí)施
很大程度上,React的性能優(yōu)化就是干掉無(wú)謂的渲染词爬。- 我們可以重寫(xiě)
shouldComponentUpdate()
方法秃嗜,對(duì)this.props與nextProps進(jìn)行比較,對(duì)this.state與nextState進(jìn)行比較顿膨,如果有改變锅锨,則返回true,否則返回false虽惭。 - 組件繼承
React.PureComponent
橡类。(自動(dòng)幫我們進(jìn)行淺比較) - 使用
pure-render-decorator
裝飾器蛇尚。(自動(dòng)幫我們進(jìn)行淺比較) - 使用
react-addons-shallow-compare
芽唇。(自動(dòng)幫我們進(jìn)行淺比較) - 使用immutable.js。
- 使用seamless-immutable取劫。
- 我們可以重寫(xiě)
- 性能分析工具
- React.addons.Perf
- react-perf-tool
參考
使用immutable優(yōu)化React
React性能優(yōu)化總結(jié)
React 源碼剖析系列 - 不可思議的 react diff
未完待續(xù)....