文:徐超,《React進階之路》做者前端
受權發佈,轉載請註明做者及出處react
###React 深刻系列5:事件處理git
React 深刻系列,深刻講解了React中的重點概念、特性和模式等,旨在幫助你們加深對React的理解,以及在項目中更加靈活地使用React。github
Web應用中,事件處理是重要的一環,事件處理將用戶的操做行爲轉換爲相應的邏輯執行或界面更新。在React中,處理事件響應的方式有多種,本文將詳細介紹每一種處理方式的用法、使用場景和優缺點。bash
先上代碼:babel
//代碼1
class MyComponent extends React.Component {
render() {
return (
<button onClick={()=>{console.log('button clicked');}}>
Click
</button>
);
}
}
複製代碼
點擊Button的事件響應函數是一個匿名函數,這應該是最多見的處理事件響應的方式了。這種方式的好處是,簡單直接。哪裏須要處理事件響應,就在哪裏定義一個匿名函數處理。代碼1中的匿名函數使用的是箭頭函數,咱們也能夠不使用箭頭函數:前端工程師
//代碼2
class MyComponent extends React.Component {
render() {
return (
<button onClick={function(){console.log('button clicked');}}>
Click
</button>
);
}
}
複製代碼
雖然代碼2的運行效果和代碼1相同,但實際項目中不多見到代碼2的這種寫法。這是由於箭頭函數解決了this綁定的問題,能夠將函數體內的this綁定到當前對象,而不是運行時調用函數的對象。若是響應函數中須要使用this.state,那麼代碼2就沒法正常運行了。因此項目中通常直接使用箭頭函數定義的匿名函數做爲事件響應。app
使用匿名函數的缺點是:當事件響應邏輯比較複雜時,匿名函數的代碼量會很大,會致使render函數變得臃腫,不容易直觀地看出組件最終渲染出的元素結構。另外,每次render方法調用時,都會從新建立一個匿名函數對象,帶來額外的性能開銷,當組件的層級越低時,這種開銷就越大,由於任何一個上層組件的變化均可能會觸發這個組件的render方法。固然,在大多數狀況下,這點性能損失是能夠沒必要在乎的。函數
代碼以下:性能
//代碼3
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {number: 0};
this.handleClick = this.handleClick.bind(this); // 手動綁定this
}
handleClick() {
this.setState({
number: ++this.state.number
});
}
render() {
return (
<div>
<div>{this.state.number}</div>
<button onClick={this.handleClick}>
Click
</button>
</div>
);
}
}
複製代碼
點擊Button的事件響應函數是組件的方法:handleClick。這種方式的好處是:每次render方法的調用,不會從新建立一個新的事件響應函數,沒有額外的性能損失。可是,使用這種方式要在構造函數中爲做爲事件響應的方法(handleClick),手動綁定this: this.handleClick = this.handleClick.bind(this)
,這是由於ES6 語法的緣故,ES6 Class 的方法默認不會把this綁定到當前的實例對象上,須要咱們手動綁定。每次都手動綁定this是否是有點繁瑣?好吧,讓咱們來看下一種方式。
使用ES7的屬性初始化語法( property initializers ),代碼能夠這樣寫:
//代碼4
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {number: 0};
}
handleClick = () => {
this.setState({
number: ++this.state.number
});
}
render() {
return (
<div>
<div>{this.state.number}</div>
<button onClick={this.handleClick}>
Click
</button>
</div>
);
}
}
複製代碼
這樣一來,不再用手動綁定this了。可是你須要知道,這個特性還處於試驗階段,默認是不支持的。若是你是使用官方腳手架Create React App 建立的應用,那麼這個特性是默認支持的。你也能夠自行在項目中引入babel的transform-class-properties插件獲取這個特性支持。
事件響應函數默認是會被傳入一個事件對象Event做爲參數的。若是想傳入其餘參數給響應函數應該怎麼辦呢?
使用第一種方式的話很簡單,直接使用新參數:
//代碼5
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [1,2,3,4],
current: 1
};
}
handleClick(item,event) {
this.setState({
current: item
});
}
render() {
return (
<ul>
{this.state.list.map(
(item)=>(
<li className={this.state.current === item ? 'current':''}
onClick={(event) => this.handleClick(item, event)}>{item}
</li>
)
)}
</ul>
);
}
}
複製代碼
onClick的響應函數中,方法體內能夠直接使用新的參數item。
使用第二種方式的話,能夠把綁定this的操做延遲到render中,在綁定this的同時,綁定額外的參數:
//代碼6
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [1,2,3,4],
current: 1
};
}
handleClick(item) {
this.setState({
current: item
});
}
render() {
return (
<ul>
{this.state.list.map(
(item)=>(
<li className={this.state.current === item ? 'current':''}
onClick={this.handleClick.bind(this, item)}>{item}
</li>
)
)}
</ul>
);
}
}
複製代碼
使用第三種方式,解決方案和第二種基本一致:
//代碼7
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [1,2,3,4],
current: 1
};
}
handleClick = (item) => {
this.setState({
current: item
});
}
render() {
return (
<ul>
{this.state.list.map(
(item)=>(
<li className={this.state.current === item ? 'current':''}
onClick={this.handleClick.bind(undefined, item)}>{item}
</li>
)
)}
</ul>
);
}
}
複製代碼
不過這種方式就有點雞肋了,由於雖然你不須要經過bind函數綁定this,但仍然要使用bind函數來綁定其餘參數。
關於事件響應函數,還有一個地方須要注意。無論你在響應函數中有沒有顯式的聲明事件參數Event,React都會把事件Event做爲參數傳遞給響應函數,且參數Event的位置老是在其餘自定義參數的後面。例如,在代碼6和代碼7中,handleClick
的參數中雖然沒有聲明Event參數,但你依然能夠經過arguments[1]
獲取到事件Event對象。
總結一下,三種事件處理的方式,第一種有額外的性能損失;第二種須要手動綁定this,代碼量增多;第三種用到了ES7的特性,目前並不是默認支持,須要Babel插件的支持,可是寫法最爲簡潔,也不須要手動綁定this。通常推薦使用第二種和第三種方式。
新書推薦《React進階之路》
做者:徐超
畢業於浙江大學,碩士,資深前端工程師,長期就任於能源物聯網公司遠景智能。8年軟件開發經驗,熟悉大前端技術,擁有豐富的Web前端和移動端開發經驗,尤爲對React技術棧和移動Hybrid開發技術有深刻的理解和實踐經驗。
2019年,iKcamp原創新書《Koa與Node.js開發實戰》已在京東、天貓、亞馬遜、噹噹開售啦!