React-事件處理詳解
對於使用者介面而言,展示只佔整體設計因素的一半,另一半則是相應使用者輸入,即通過JavaScript處理使用者產生的事件。
React通過將事件處理器 繫結到組建上處理事件,事件觸發的同時更新組建的內部狀態,內部狀態更新會觸發元件的重繪。因此,如果檢視層想要渲染出事件出發後的結果,它所要做的就是渲染函式中讀取元件的內部狀態。
一、繫結事件處理器
React處理事件本身和原生的JavaScript事件一樣:MouseEvents事件用於點選處理器,Change事件用於表單元素變化,等等,所有事件在命名上與原生的JavaScript規範一致,並且會在相同的情境下被觸發,React繫結事件處理器的語法和HTML語法非常相似。比如,在我們的問卷製作工具例項中,包含了下面的程式碼,在Save按鈕上 繫結onclick事件處理器。
<button className ='btn btn-save'onClick={this.handleSaveClicked}>Save</button>
使用者點選這個按鈕時,元件的handleSaveClicked方法會被呼叫。這個方法中包含處理Save行為的邏輯。
PS:這個程式碼可在寫法上類似普通不推薦的HTML內聯事件處理器屬性,比如:onClick,但其實在底層實習那上冰沒有使用HTML的onClick屬性。React只是用這種寫法繫結事件處理器,但內部則是按照需要高效的維護著時間處理器。
如果不用JSX語法,你可以選擇在引數物件的屬性上指定事件處理器,比如:
React.DOM.button({ClassName:'btnbtn-save',onClick:this.handleSaveClicked},"Save");
其中大部分事件不需要而外的處理就可以工作,但是觸控事件需要通過呼叫一下程式碼手動啟動:
React.initializeTouchEvent(true);
二、事件和狀態
1、設想你需要讓一個元件隨著使用者的輸入而改變,比如在問卷編輯器中,你想要讓使用者從一個問題型別的選單當中拖拽問卷問題。
首先,在渲染函式內部基於HTML5拖放(Drag and Drop)API註冊事件處理器,程式碼如下:
var SurveyEdit =React.creatclass({
render:function(){
<div className='survey-edit'>
<div className='row'>
<aside className='sidebar col-md-3'>
<h2>Module</h2>
<DraggableQuestions>
</aside>
<div className='survey-canvas col-md-9'>
<div
className ={'drop-zone well well-drop-zone'}
onDragOver={this.handleDragOver}
onDragEnter={this.handleDragEnter}
onDragLeave={this.handleDragLeave}
onDrop ={this.handleDrop}
>
Drag and drop a module from the left
</div>
</div>
</div>
</div>
},
});
三、根據狀態進行渲染
事件處理器方法還需要完成一件事——展開當前已經加入的題目清單。為了實現該功能,你需要充分利用每個React元件的內部狀態,元件預設是null,但是可以通過它的getInitialState方法將其出事話為合理的值,比如:
getInitaSate:funciton (){
return {
dropZoneEntered:false,
title:’‘,
introduction:' ',
questions:{}
};
}
以上程式碼元件初始化了預設值:一個空標題、一個空的介紹、一組空的題目以及一個值為false的dropZoeEntered屬性,用於表示當前使用者沒有拖拽任何內容那個到放置區域。
到這裡,你已經可以在render方法當中讀取this.state,以便向用戶展示當前表單中所有資料了。
四、更新狀態
更新元件內部狀態會出發元件重繪,所以接下來要做的事情就是在拖拽的事件處理器方法中更新狀態,然後再次執行render函式,它會從this.state中讀取新資料來顯示標題,介紹及題目,使用者會看到所有內容被正確的更新。
更新元件狀態有兩種方案:元件的setState方法和repaceState方法。replaceState用一個全新的state物件完整的替換掉原有的state。使用不可變資料結構表示狀態時,這種方式很有效,不過很少應用用於其他場景下。更多的情況下回使用setState,它僅僅會把傳入的物件合併到已有的state物件。
例如:
getInitialState:funciton(){
return {
dropZoneEntered:false,
title:'Fantastic Survey',
introduction:'This survey isfantastic!',
question :[]
};
}
這時,呼叫this.setState({title:"Fantasticsurvey 2.0"})僅僅影響this.setState.title的值,而this.state.dropZoneEntered、this.state.introduction及this.state.questions不會受影響。
而如果呼叫this.replaceState({title:"FantasticSurvey 2.0"}),則會用新的物件{title:“Fantastic Survey 2.0}替換掉整個state物件,同時把this.state.dropZone-Entered]this.state.introduction和this.state.questions全部清除掉。這樣做很可能中斷render函式執行,因為它期望this.state.questions是一個數組而不是undefined。現在使用this.setState可以實現上文中提到事件處理器方法。
使用this.State可以實現上文提到的事件處理器方法。
handleFormChange:function(formData){
this.setSate(formData);
}.
handleDragOver:function(ev){
//這保證 handleDroZoneDrop可以被呼叫
ev.preventDefault();
},
handleDragEnter:function(){
this.setState({dropZoneEntered:true});
},
handleDragLeave:function(){
this.setState({dropZoneEntered:false});
},
handleDrop:function(ev){
var questionType =ev.dataTransfer.getData('questionType');
var questions =this.state.questions;
questions = questions.concat({type:questionType});
this.setState({
questions:questions,
dropZoneEntered:false
});
});
這一點很重要永遠不要嘗試通過setState或者replaceState以外的方法去修改state物件,類似this.state.saveInProgress =true通常不是一個好主意,因為它無法通知React師傅需要重新渲染元件,而且可能回答熬製下次呼叫setState時出現意外結果。
五、事件物件
很多事件處理器只要觸發就會完成功能,但有時也會需要關於使用者輸入的更多資訊。例如:
var AnswerEssayQuestion=React.createClass({
handleComplete:functin(event){
this.callMethodOnProps('onCompleted',event.target.value);
},
},
render:function(){
return(
<div className="form-group">
<lavel className="survey-item-label">{this.props.label}</label>
<div className="survey-item-content">
<textarea className="form-control" rows="3"onBlur={this.handleComplete}/>
</div>
</div>
);
}
通常會有一個事件物件傳入到React的事件處理函式中,類似於原生的JavaScript事件監聽器的寫法。這裡的handleComplete方法會接受一個事件物件,並通過存取event.target.value值為textarea賦值,在事件處理器中,使用event.target.value獲取表單中input值是一個常規的方法,尤其在onClange事件處理器中。
注意:callMethodOnProps是由一個叫做PropsMethodMixin的mixin提供的,此外這個mixin還提供了一些處理父元件之間通訊的簡便辦法。
React把原生的事件封裝在一個SyntheticEvent例項中,而不是直接把原生的瀏覽器事件傳給處理器,SyntheticEvent在表現和功能上都與瀏覽器的原生事件一致,而且消除了跨瀏覽器差異。
對於使用者介面而言,展示只佔整體設計因素的一半,另一半則是相應使用者輸入,即通過JavaScript處理使用者產生的事件。
React通過將事件處理器 繫結到組建上處理事件,事件觸發的同時更新組建的內部狀態,內部狀態更新會觸發元件的重繪。因此,如果檢視層想要渲染出事件出發後的結果,它所要做的就是渲染函式中讀取元件的內部狀態。
一、繫結事件處理器
React處理事件本身和原生的JavaScript事件一樣:MouseEvents事件用於點選處理器,Change事件用於表單元素變化,等等,所有事件在命名上與原生的JavaScript規範一致,並且會在相同的情境下被觸發,React繫結事件處理器的語法和HTML語法非常相似。比如,在我們的問卷製作工具例項中,包含了下面的程式碼,在Save按鈕上 繫結onclick事件處理器。
<button className ='btn btn-save'onClick={this.handleSaveClicked}>Save</button>
使用者點選這個按鈕時,元件的handleSaveClicked方法會被呼叫。這個方法中包含處理Save行為的邏輯。
PS:這個程式碼可在寫法上類似普通不推薦的HTML內聯事件處理器屬性,比如:onClick,但其實在底層實習那上冰沒有使用HTML的onClick屬性。React只是用這種寫法繫結事件處理器,但內部則是按照需要高效的維護著時間處理器。
如果不用JSX語法,你可以選擇在引數物件的屬性上指定事件處理器,比如:
React.DOM.button({ClassName:'btnbtn-save',onClick:this.handleSaveClicked},"Save");
其中大部分事件不需要而外的處理就可以工作,但是觸控事件需要通過呼叫一下程式碼手動啟動:
React.initializeTouchEvent(true);
二、事件和狀態
1、設想你需要讓一個元件隨著使用者的輸入而改變,比如在問卷編輯器中,你想要讓使用者從一個問題型別的選單當中拖拽問卷問題。
首先,在渲染函式內部基於HTML5拖放(Drag and Drop)API註冊事件處理器,程式碼如下:
var SurveyEdit =React.creatclass({
render:function(){
<div className='survey-edit'>
<div className='row'>
<aside className='sidebar col-md-3'>
<h2>Module</h2>
<DraggableQuestions>
</aside>
<div className='survey-canvas col-md-9'>
<div
className ={'drop-zone well well-drop-zone'}
onDragOver={this.handleDragOver}
onDragEnter={this.handleDragEnter}
onDragLeave={this.handleDragLeave}
onDrop ={this.handleDrop}
>
Drag and drop a module from the left
</div>
</div>
</div>
</div>
},
});
三、根據狀態進行渲染
事件處理器方法還需要完成一件事——展開當前已經加入的題目清單。為了實現該功能,你需要充分利用每個React元件的內部狀態,元件預設是null,但是可以通過它的getInitialState方法將其出事話為合理的值,比如:
getInitaSate:funciton (){
return {
dropZoneEntered:false,
title:’‘,
introduction:' ',
questions:{}
};
}
以上程式碼元件初始化了預設值:一個空標題、一個空的介紹、一組空的題目以及一個值為false的dropZoeEntered屬性,用於表示當前使用者沒有拖拽任何內容那個到放置區域。
到這裡,你已經可以在render方法當中讀取this.state,以便向用戶展示當前表單中所有資料了。
四、更新狀態
更新元件內部狀態會出發元件重繪,所以接下來要做的事情就是在拖拽的事件處理器方法中更新狀態,然後再次執行render函式,它會從this.state中讀取新資料來顯示標題,介紹及題目,使用者會看到所有內容被正確的更新。
更新元件狀態有兩種方案:元件的setState方法和repaceState方法。replaceState用一個全新的state物件完整的替換掉原有的state。使用不可變資料結構表示狀態時,這種方式很有效,不過很少應用用於其他場景下。更多的情況下回使用setState,它僅僅會把傳入的物件合併到已有的state物件。
例如:
getInitialState:funciton(){
return {
dropZoneEntered:false,
title:'Fantastic Survey',
introduction:'This survey isfantastic!',
question :[]
};
}
這時,呼叫this.setState({title:"Fantasticsurvey 2.0"})僅僅影響this.setState.title的值,而this.state.dropZoneEntered、this.state.introduction及this.state.questions不會受影響。
而如果呼叫this.replaceState({title:"FantasticSurvey 2.0"}),則會用新的物件{title:“Fantastic Survey 2.0}替換掉整個state物件,同時把this.state.dropZone-Entered]this.state.introduction和this.state.questions全部清除掉。這樣做很可能中斷render函式執行,因為它期望this.state.questions是一個數組而不是undefined。現在使用this.setState可以實現上文中提到事件處理器方法。
使用this.State可以實現上文提到的事件處理器方法。
handleFormChange:function(formData){
this.setSate(formData);
}.
handleDragOver:function(ev){
//這保證 handleDroZoneDrop可以被呼叫
ev.preventDefault();
},
handleDragEnter:function(){
this.setState({dropZoneEntered:true});
},
handleDragLeave:function(){
this.setState({dropZoneEntered:false});
},
handleDrop:function(ev){
var questionType =ev.dataTransfer.getData('questionType');
var questions =this.state.questions;
questions = questions.concat({type:questionType});
this.setState({
questions:questions,
dropZoneEntered:false
});
});
這一點很重要永遠不要嘗試通過setState或者replaceState以外的方法去修改state物件,類似this.state.saveInProgress =true通常不是一個好主意,因為它無法通知React師傅需要重新渲染元件,而且可能回答熬製下次呼叫setState時出現意外結果。
五、事件物件
很多事件處理器只要觸發就會完成功能,但有時也會需要關於使用者輸入的更多資訊。例如:
var AnswerEssayQuestion=React.createClass({
handleComplete:functin(event){
this.callMethodOnProps('onCompleted',event.target.value);
},
},
render:function(){
return(
<div className="form-group">
<lavel className="survey-item-label">{this.props.label}</label>
<div className="survey-item-content">
<textarea className="form-control" rows="3"onBlur={this.handleComplete}/>
</div>
</div>
);
}
通常會有一個事件物件傳入到React的事件處理函式中,類似於原生的JavaScript事件監聽器的寫法。這裡的handleComplete方法會接受一個事件物件,並通過存取event.target.value值為textarea賦值,在事件處理器中,使用event.target.value獲取表單中input值是一個常規的方法,尤其在onClange事件處理器中。
注意:callMethodOnProps是由一個叫做PropsMethodMixin的mixin提供的,此外這個mixin還提供了一些處理父元件之間通訊的簡便辦法。
React把原生的事件封裝在一個SyntheticEvent例項中,而不是直接把原生的瀏覽器事件傳給處理器,SyntheticEvent在表現和功能上都與瀏覽器的原生事件一致,而且消除了跨瀏覽器差異。