書籍完整目錄
1.5 React 與 DOM
在這一節中,主要的討論範圍為 React 與 DOM 相關的處理,包括:
-
如何獲取 DOM 元素
-
如何做事件響應處理
-
表單處理
-
style 屬性
這節講述過後,我們將會為 TODO 應用添加完整的事件響應,包括新增,刪除,標記完成等。
1.5.1 獲取 DOM 元素
上一節我們已經講過組件的生命週期,DOM 真正被添加到 HTML 中的 hook 為
-
componentDidMount
-
componentDidUpdate
在這兩個 hook 函數中, 我們可以獲取真正的 DOM 元素,React 提供的獲取方法兩種方式
findDOMNode()
通過 ReactDOM 提供的 findDOMNode 方法, 傳入參數我組件實例,eg
var MyComponent = React.createClass({
render: function() {
return <div> .... </div>
},
componentDidMount: function() {
var $root = ReactDOM.findDOMNode(this);
console.log($root);
}
})
需要注意的是此方法不能應用到無狀態組件上
Refs
上面的方法只能獲取到 root 元素,那如果我的 DOM 有很多層級,我想獲取一個子級的元素呢?
React 提供了 ref 屬性來實現這種需求。
每個組件實例都有一個 this.refs 屬性,會自動引用所有包含 ref 屬性組件的 DOM, eg:
var MyComponent = React.createClass({
render: function() {
return <div>
<button ref="btn">...</button>
<a href="" ref="link"></a>
</div>
},
componentDidMount: function() {
var $btn = this.refs.btn;
var $link = this.refs.link;
console.log($btn, $link);
}
})
1.5.2 DOM 事件
官方事件文檔 http://facebook.github.io/react/docs/events.html
綁定事件
在 React 中綁定事件的方式很簡單,只需要在元素中添加事件名稱的屬性已經對應的處理函數,如:
var MyComponent = React.creatClass({
render: function() {
return <div>
<button onClick={this.onClick}>Click Me</button>
</div>
},
onClick: function() {
console.log('click me');
}
});
事件名稱和其他屬性名稱一樣,服從駝峯式命名。
合成事件(SyntheticEvent)
在 React 中, 事件的處理由其內部自己實現的事件系統完成,觸發的事件都叫做 合成事件(SyntheticEvent),事件系統對瀏覽器做了兼容,其提供的 API 與原生的事件無異。
boolean bubbles
boolean cancelable
DOMEventTarget currentTarget
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent
void preventDefault()
boolean isDefaultPrevented()
void stopPropagation()
boolean isPropagationStopped()
DOMEventTarget target
number timeStamp
string type
和原生事件的區別在於,事件不能異步話,如:
function onClick(event) {
console.log(event); // => nullified object.
console.log(event.type); // => "click"
var eventType = event.type; // => "click"
setTimeout(function() {
console.log(event.type); // => null
console.log(eventType); // => "click"
}, 0);
this.setState({clickEvent: event}); // Won't work. this.state.clickEvent will only contain null values.
this.setState({eventType: event.type}); // You can still export event properties.
}
原因是在事件系統的內部實現當中, 一個事件對象可能會被重用(也就是事件做了池化 Pooling)。當一個事件響應函數執行過後,事件的屬性被設置為 null, 如果想用保持事件的值的話,可以調用
event.persist()
這樣,屬性會被保留,並且事件也會被從池中取出。
事件捕獲和冒泡
在 DOM2.0 事件分為捕獲階段和冒泡階段,React 中通常我們註冊的事件為冒泡事件,如果要註冊捕獲階段的事件,可以在事件名稱後加 Capture 如:
onClick
onClickCapture
支持事件列表
粘貼板事件 {
事件名稱:onCopy onCut onPaste
屬性:DOMDataTransfer clipboardData
}
編輯事件 {
事件名稱:onCompositionEnd onCompositionStart onCompositionUpdate
屬性:string data
}
鍵盤事件 {
事件名稱:onKeyDown onKeyPress onKeyUp
屬性: {
boolean altKey
number charCode
boolean ctrlKey
boolean getModifierState(key)
string key
number keyCode
string locale
number location
boolean metaKey
boolean repeat
boolean shiftKey
number which
}
}
// 焦點事件除了表單元素以外,可以應用到所有元素中
焦點事件 {
名稱:onFocus onBlur
屬性:DOMEventTarget relatedTarget
}
表單事件 {
名稱:onChange onInput onSubmit
}
鼠標事件 {
名稱:{
onClick onContextMenu onDoubleClick onDrag onDragEnd onDragEnter onDragExit onDragLeave onDragOver onDragStart onDrop onMouseDown onMouseEnter onMouseLeave onMouseMove onMouseOut onMouseOver onMouseUp
}
屬性:{
boolean altKey
number button
number buttons
number clientX
number clientY
boolean ctrlKey
boolean getModifierState(key)
boolean metaKey
number pageX
number pageY
DOMEventTarget relatedTarget
number screenX
number screenY
boolean shiftKey
}
}
選擇事件 {
名稱:onSelect
}
觸摸事件 {
名稱:onTouchCancel onTouchEnd onTouchMove onTouchStart
屬性:{
boolean altKey
DOMTouchList changedTouches
boolean ctrlKey
boolean getModifierState(key)
boolean metaKey
boolean shiftKey
DOMTouchList targetTouches
DOMTouchList touches
}
}
UI 事件 {
名稱:onScroll
屬性:{
number detail
DOMAbstractView view
}
}
滾輪事件 {
名稱:onWheel
屬性:{
number deltaMode
number deltaX
number deltaY
number deltaZ
}
}
媒體事件 {
名稱:{
onAbort onCanPlay onCanPlayThrough onDurationChange onEmptied onEncrypted onEnded onError onLoadedData onLoadedMetadata onLoadStart onPause onPlay onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend onTimeUpdate onVolumeChange onWaiting
}
}
圖像事件 {
名稱:onLoad onError
}
動畫事件 {
名稱:onAnimationStart onAnimationEnd onAnimationIteration
屬性:{
string animationName
string pseudoElement
float elapsedTime
}
}
漸變事件 {
名稱:onTransitionEnd
屬性: {
string propertyName
string pseudoElement
float elapsedTime
}
}
1.5.3 表單事件
在 React 中比較特殊的事件是表單事件,大多數組件都是通過屬性和狀態來決定的,但是表單組件如 input, select, option 這些組件的狀態用户可以修改,在 React 中會特殊處理這些組件的事件。
onChange 事件
和普通 HTML 中的 onChange 事件不同, 在原生組件中,只有 input 元素失去焦點才會觸發 onChange 事件, 在 React 中,只要元素的值被修改就會觸發 onChange 事件。
var MyComponent = React.createClass({
getInitialState: function() {
return {
value: ''
}
},
render: function() {
return <div onChange={this.onChangeBubble}>
<input value={this.state.value} onChange={this.onChange}/>
</div>
},
onChange: function(ev) {
console.log('change: ' + ev.target.value);
this.setState({
value: ev.target.value
});
},
// onChange 事件支持所有組件,可以被用於監聽冒泡事件
onChangeBubble: function(ev) {
console.log('bubble onChange event', + ev.target.value);
}
})
交互屬性
表單組件中能被用户修改的屬性叫交互屬性,包括:
-
value=> <input> 和 <select> 組件 -
checked=> <input type="checkbox|radio"> -
selected=> <opiton>
textara
在 HTML 中,textarea 的值是像如下定義的:
<textarea name="" id="" cols="30" rows="10">
some value
</textarea>
而在 React 中, TextArea 的使用方式同 input 組件,使用 value 來設置值
var MyComponent = function() {
render: function() {
return <div>
<textarea value={...} onChange={...}/>
</div>
}
}
select 組件
在 React 中 select 組件支持 value 值,value 值還支持多選
<select value="B">
<option value="A">Apple</option>
<option value="B">Banana</option>
<option value="C">Cranberry</option>
</select>
<select multiple={true} value={['B', 'C']}>
<option value="A">Apple</option>
<option value="B">Banana</option>
<option value="C">Cranberry</option>
</select>
受控組件
在 React 中表單組件可分為兩類,受控與非受控組件,受控組件是包含了 value 值的,如:
render: function() {
return <input type="text" value="....."/>
}
為什麼叫受控組件? 因為這個時候用户不能修改 input 的值, input 的值永遠是 value 固定了的值。
如果去掉 value 屬性,那麼就可以輸入值了。
那如何修改受控組件的值呢? 如上面的例子中, 添加 onChange 事件,事件內修改 value 屬性,value 屬性的值會被設置到組件的 value 中。
非受控組件
對應受控組件,也有非受控組件,那麼那種是非受控組價呢?
沒有 value 值的 input
render: function() {
return <input type="text"/>
}
那如果想設置默認值呢?
可以通過 defaultValue 屬性來設置
render: function() {
return <input type="text" defaultValue="Default Value">
}
類似的對於 checkbox 有 defaultChecked 屬性
需要注意的是,默認值只適用於第一次渲染,在重渲染階段將不會適用。
checkbox 和 radio
checkbox 和 radio 比較特殊, 如果在 onChange 事件中調用了 preventDefault ,那麼瀏覽器不會更新 checked 狀態,即便事實上組件的值已經 checked 或者 unchecked 了 。
eg:
var CheckBox = React.createClass({
getInitialState: function(){
return {
checked: false
}
},
render: function() {
return <div>
<input type="checkbox"
checked={this.state.checked}
onChange={this.onChange}/>
</div>
},
onChange: function(ev) {
this.setState({
checked: true
});
ev.preventDefault();
}
})
這個例子裏邊,checked 雖然更新為 true ,但是 input 的值 checked 為 false
那應如何處理 checkbox 呢?
-
避免調用 ev.preventDefault 就行
-
在 setTimeout 中處理 checked 的修改
-
使用 click 事件
1.5.4 style 屬性
在 React 中,可以直接設置 style 屬性來控制樣式,不過與 HTML 不同的是, 傳入的 style 值為一個對象, 對象的所有 key 都是駝峯式命名,eg:
render: function() {
var style = {
backgroundColor: 'red',
height: 100,
width: 100
}
return <div style={style}></div>
}
其中還可以看到不同的地方時,為了簡寫寬度高度值,可以直接設置數字,對應 100 -> 100px。如果某些屬性不需要添加 px 後綴,React 也會自動去除。
通過屬性值駝峯式的原因是 DOM 內部訪問 style 也是駝峯式。如果需要添加瀏覽器前綴瑞 -webkit-、-ms- 大駝峯(除了 ms ), 如:
var divStyle = {
WebkitTransition: 'all', // 'W' 是大寫
msTransition: 'all' // 'ms' 為小寫
};
為什麼要用 inline 的樣式?
在以前的前端開發方式是 樣式結構和邏輯要分離, 而現在 React 中卻有很多人推崇 inline 的樣式。 在我看來因人而異,React 的這種模式也能做到樣式模塊化,樣式重用(借用 Js 的特點)。並且因為 React 的實現方式,Inline 樣式的性能甚至比 class 的方式高。
1.5.5 實例練習:完整功能的 TODO 應用
@todo