React 深入系列,深入講解了React中的重點概念、特性和模式等,旨在幫助大家加深對React的理解,以及在專案中更加靈活地使用React。
Web應用中,事件處理是重要的一環,事件處理將使用者的操作行為轉換為相應的邏輯執行或介面更新。在React中,處理事件響應的方式有多種,本文將詳細介紹每一種處理方式的用法、使用場景和優缺點。
使用匿名函式
先上程式碼:
//程式碼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就無法正常執行了。所以專案中一般直接使用箭頭函式定義的匿名函式作為事件響應。
使用匿名函式的缺點是:當事件響應邏輯比較複雜時,匿名函式的程式碼量會很大,會導致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 深入系列6:高階元件
我的新書《React進階之路》已上市,對React感興趣的同學不妨去了解下。 購買地址: 噹噹 京東
歡迎關注我的公眾號:老幹部的大前端