react事件
由于react的虛擬DOM模式,react對所有的事件也都做了專門的處理和優化,官方的說法是引入了虛擬事件對象,將瀏覽器的事件進行了封裝,有著瀏覽器本地事件西安通的屬性和方法,但是沒有兼容問題。聽起來不錯,回到我們之前state的demo,handleClick(e){}
加一個console.log(e)
來打印一下這個點擊事件:
這里可以看到我們打印出來的全部是空,這是因為react給我們做了處理,把不需要的值全部隱藏了。現在我們來改一下console.log(e.target)
:
可以看到target已經被打印出來了。如果你有時候需要的是本地的event的話可以使用e.nativeEvent來調用:
這里就是瀏覽器原生的event事件,但是需要注重瀏覽器兼容性。所以一般情況下最好使用react封裝好的event事件,react支持的完整事件列表可以在官方文檔查看,這里不再一一列舉。
tip:如果需要阻止默認的事件,比如form表單的默認提交,a標簽的href... 必須使用e.preventDefault() 。而需要阻止事件冒泡則需使用e.stopPropagation()。
React事件機制不同于我們以往的js事件,為提高效率,它對事件做了二次處理,將所有項目中注冊的事件全部代理(delegate)到頂層的document上,當你點擊某個DOM的時候,它會找到這個DOM和其Component,并冒泡到找到對應的事件并觸發。
比如我們經典的 ul li 結構,傳統js使用事件委托將事件綁定在ul上,而現在使用react只需要在ul上綁一個click事件,每次點擊ul里面的元素都會冒泡并觸發到ul的事件上去。但是要注意如果你在ul里面再綁定一個事件,同時不使用stopPropagation去阻止這個冒泡事件,那他會一直執行到最頂層的事件上去。
想要深入了解React事件機制?相信這篇博客會對你有所幫助——React源碼解讀系列 -- 事件機制
Refs & 真實DOM
由于react使用虛擬DOM的結構,所有的頁面變動都先在虛擬DOM上處理,只有當他們真正插入到文檔中才會轉化成真實DOM,react正是通過這樣的機制來減少DOM操作從而提高頁面的性能。
但是有時候我們確實有獲取DOM的需求,前面在學習事件的時候我們使用e.target可以獲取到真實的DOM。
而同時react也給我們提供了refs 來處理類似的情況。ref在react中是一個特殊的屬性,它可以是一個回調函數也可以是一個字符串。如果你查找國內的資料你會發現幾乎所有人用的都是ref的字符串屬性,但是在最新版(V15.4.2)的react文檔中,ref的字符串屬性被稱為’Legacy API‘ ,官方強調字符串的ref存在一些問題,并且很可能在未來的版本中將其移除。
我們先來看一下官方的demo:
var MyComponent = React.createClass({
handleClick: function() {
this.textInput.focus();
console.log(this);
console.log(this.textInput)
},
render: function() {
return (
<div>
<input type="text" ref={(input) => { this.textInput = input; }} />
<input type="button" value="Focus the text input" onClick={this.handleClick} />
</div>
);
}
});
ReactDOM.render(
<MyComponent />,
document.getElementById('example')
);
ref的回調函數會在組件創建或者重新渲染時立即執行,回調函數的參數即為當前組件的真實DOM,我們可以立即使用這個DOM或者將其保存以備后用。
上面代碼中輸入框的ref使用es6語法直接在組件中創建了一個回調函數,這個函數將參數input保存到this.textInput中,我這里把 this 和 this.textInput 打印出來來幫助大家更好去理解:
這里this指向ReactElement,即當前的的react組件,通過回調函數往this里添加了textInput對象。通過下面的this.textInput就可以看到textInput為該組件的原生DOM,我們通過ref的回調函數將該DOM存在this中,方便后面隨時調用。
簡單的ref回調我們可以直接寫在標簽中,如果復雜的話我們可以將其抽出為一個函數,在標簽中通過 ref={this.funtionName}
的形式調用。
同時我這里也有一個string refs的demo,雖然現在有很多人這樣用,但是我們要盡量避免這樣的寫法:
var MyComponent = React.createClass({
handleClick: function() {
this.refs.myTextInput.focus();
},
render: function() {
return (
<div>
<input type="text" ref="myTextInput" />
<input type="button" value="Focus the text input" onClick={this.handleClick} />
</div>
);
}
});
ReactDOM.render(
<MyComponent />,
document.getElementById('example')
);
這里給ref定義一個字符串,通過refs獲取到頁面所有的ref,再通過ref的字符串定位到該DOM,這樣操作確實方便很多,所以這個方法被廣為流傳,但是建議有使用這樣方法的童鞋最好慢慢拋棄這種寫法。
tips:有些童鞋可能網上查找資料會看到getDOMNode() or findDOMNode() 方法,在早期react中確實存在這些方法結合ref來獲取DOM,但是后來React拆分出ReactDOM后這些方法被歸到ReactDOM 的方法中,直接在react中是不能使用的。
react表單
在開始之前我們先看一個例子:
var Form = React.createClass({
render: function() {
return (
<input type='text' value='這是一個輸入框'/>
)
}
})
ReactDOM.render(
<Form/>,
document.getElementById('example')
);
乍一看是不是和我們平時HTML的寫法基本一樣,但是一運行你就會發現無論如何你改變不了input中的值,鍵盤的輸入對它沒有任何影響!
react官方將帶有value的表單定義為受限組件 ,即該組件受限制于value值,他將永遠等于value的值。當然,不代表我們就不能定義表單的默認值了,官方給出了兩種解決辦法:
1,使用defaultValue。既然有受限組件,那就存在不受限組件。官方定義為不設置value的組件就是一個不受限組件,而不受限組件可以實時響應用戶的輸入將其反映到元素上。所以react就給組件定義了defaultValue來給組件設置一個非空的初始值,而對于radio,checkbox則有對應的defaultCheckd屬性來代替checkd:
<input type='text' defaultValue='這里是默認值,也可以使用state'/>
我們可以給input定義ref的回調來獲取輸入的值:
<input type='text' ref={(input) => { this.textInput = input; }} defaultValue='這里是默認值,也可以使用state'/>
然后通過事件獲取this.textInput.value。
比如我們一個頁面有很多輸入框,可以給他們分別定義ref,然后通過submit按鈕的提交事件來一次性全部獲取所有的輸入值。
tip:如果我們需要阻止默認的表單提交事件,需要使用e.preventDefault(),這一點我們在之前的事件里提到過。 ( ) => { }
為ES6語法,它表示為 function( ) { }
。
2,對于使用state來給組件設置初始值的,可以給組件綁定onChange事件,通過onChange來實時獲取用戶輸入,動態的改變state的值。這樣雖然組件受限于value,但是value的值發生了變化,那么組件的值也就跟著變化:
var Form = React.createClass({
getInitialState:function(){
return {
inputValue: 'input',
}
},
handleInput:function(e) {
this.setState({inputValue: e.target.value});
},
render: function() {
return (
<input type='text' value={this.state.inputValue} onChange={this.handleInput}/>
)
}
})
ReactDOM.render(
<Form/>,
document.getElementById('example')
);
以上是使用e.target.value來獲取輸入值來刷新state,當然你也可以使用ref來獲取。
如果頁面中有很多表單,但是我們又不想使用上面的defaultValue+ref,或者我們需要在用戶輸入的時候就獲取輸入值來檢查。豈不是每一個表單組件都要定義一個onChange事件?
官方也考慮到這種問題,所以給我們提供了一個更好的解決方案:
var MyComponent = React.createClass({
getInitialState: function() {
return {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
},
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
},
render: function() {
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
});
ReactDOM.render(
<MyComponent />,
document.getElementById('example')
);
通過一個onChange事件來解決所有的表單,是不是很酷!
tip: [name]
為ES6語法,它表示在對象中,name為一個變量。
tip:<textarea>
常規設置默認值采用 <textarea>這里是默認值</textarea>
,<select>
常規設置默認值是在 <option>
中添加selected,而這對于React來說會顯得很奇怪,且不方便在后續的使用中來更新默認值。所以React中所有表單的默認值設置全部使用value(defaultValue)來定義,詳細說明參見官方文檔。
生命周期
組件的生命周期分為三種:
- Mounting:已插入真實 DOM
- Updating:正在被重新渲染
- Unmounting:已移出真實 DOM
react為每個狀態提供兩種處理函數,will在函數狀態之前調用,did則在函數狀態之后調用:
- componentWillMount()
- componentDidMount()
- componentWillUpdate(object nextProps, object nextState)
- componentDidUpdate(object prevProps, object prevState)
- componentWillUnmount()
此外還有三種特殊狀態的處理函數:
- constructor():組件調用之前的構造函數,早于componentWillMount(),常用于聲明props和state
- componentWillReceiveProps(object nextProps):已加載組件收到新的參數時調用
- shouldComponentUpdate(object nextProps, object nextState):組件判斷是否重新渲染時調用