博客 / 詳情

返回

精益 React 學習指南 (Lean React)- 1.5 React 與 DOM

書籍完整目錄

1.5 React 與 DOM

圖片描述

在這一節中,主要的討論範圍為 React 與 DOM 相關的處理,包括:

  1. 如何獲取 DOM 元素

  2. 如何做事件響應處理

  3. 表單處理

  4. 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);
    }
})

交互屬性

表單組件中能被用户修改的屬性叫交互屬性,包括:

  1. value => <input><select> 組件

  2. checked => <input type="checkbox|radio">

  3. 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 呢?

  1. 避免調用 ev.preventDefault 就行

  2. 在 setTimeout 中處理 checked 的修改

  3. 使用 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

user avatar zhangxishuo 頭像
1 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.