React 起源於 Facebook 的內部項目,因為該公司對市場上所有 JavaScript MVC 框架,都不滿意,就決定自己寫一套,用來架設 Instagram 的網站。做出來以後,發現這套東西很好用,就在2013年5月就開源了。由於 React 的設計想法極為獨特,屬於革命性創新,因此性能出眾,但程式碼邏輯卻非常簡單。所以,越來越多人開始關注和使用,認為它可能是未來的 Web 開發的主流工具。
前幾天寫的那個拖拽,自己留下的疑問。 。 。這次在熱心博友的提示下又修正了一些小小的bug,也加了拖曳的邊緣偵測部分。 。 。就再聊聊拖曳吧
一、不要直接操作dom元素
react中使用了虛擬dom的概念,目地就是要盡量避免直接操作dom元素,所以我們在對dom元素進行操作的時候需要注意,我之前為了獲取form的參數就直接用了var dragBox=document .getElementById('form')去找dom,但其實記錄from的初始位置,可以在其子元件更新父元件參數的時候呼叫。即在MyFrom組件中獲取,如下代碼:
onChildChanged:function(newState){ /*以下为修改处*/ var computedStyle=document.defaultView.getComputedStyle(ReactDOM.findDOMNode(this.refs.dragBox),null); newState.left=computedStyle.left; newState.top=computedStyle.top; /*以上为修改处*/ this.setState(newState); },
這樣就可以直接在父元件中操作自己,而不是在子元件中呼叫。
二、onmousemove和onmouseup事件應該綁定到document上
拖曳事件中,當滑鼠在DragArea中按下後,就應該偵測滑鼠在document中移動的距離及何時彈起。否則直接綁定在form的話會有一個不雅的地方,就是拖動條拖動邊緣附近的時候,如果滑鼠速度快一點會失效,滑鼠再回來拖曳條會自動吸上滑鼠。因此利用react初始化階段的componentDidMount函數,這個函數是元件被裝載後才會被調用,也就是說調用這個方法的時候,元件已經被渲染到了頁面上,這個時候可以修改DOM。也就是說此時把對應事件再綁定到document上面,如下碼:
componentDidMount:function(){ document.addEventListener('mousemove',(e)=>{this.move(e);},false);/*ES6新特性,箭头函数,需要依赖jsx编译工具才能正确运行*/ document.addEventListener('mouseup',(e)=>{this.endDrag(e);},false); },
這樣就可以消除那個小小的bug啦!
三、增加邊緣偵測
一般情況下的拖曳,我們都是不希望能夠拖出可視窗口之外的,因此這就需要檢測。偵測四個方向上的位置,即上、下、左、右。顯然,上的距離top和左邊left的距離必須大於等於0,下邊和右的距離必須要小於視窗大小減去from本身的元素寬高。
具體代碼:
move:function(event){ var e = event ? event : window.event; var dBox=ReactDOM.findDOMNode(this.refs.dragBox); if (this.state.flag) { var nowX = e.clientX, nowY = e.clientY; var disX = nowX - this.state.currentX, disY = nowY - this.state.currentY; /*增加拖拽范围检测*/ var currentLeft=parseInt(this.state.left) + disX; var currentTop=parseInt(this.state.top) + disY; var docX=document.documentElement.clientWidth||document.body.clientWidth; var docY=document.documentElement.clientHeight||document.body.clientHeight; if(currentLeft<=250){//检测屏幕左边,因为我这里的初始居中是利用了负1/2的盒子宽度的margin,所以用250px判断边界 dBox.style.left=250+"px"; }else if(currentLeft>=(docX-dBox.offsetWidth+250)){ //检测右边 dBox.style.left=(docX-this.state.offsetX)+"px"; }else{ dBox.style.left =currentLeft+ "px"; } if(currentTop<=200){ //检测屏幕上边,因为我这里的初始居中是利用了负1/2的盒子高度的margin,所以用200px判断边界 <br> dBox.style.top=200+"px"; <br> }else if(currentTop>=(docY-dBox.offsetHeight+200)){ //检测下边<br> dBox.style.top=(docY-this.state.offsetY)+"px";<br> }else{<br> dBox.style.top = currentTop + "px"; <br> }<br> }
PS:新的程式碼已經更新在我的github上面,大家可以研究一下。
ReactJS的背景與原理
在Web開發中,我們總是需要將變化的資料即時反應到UI上,這時就需要對DOM進行操作。而複雜或頻繁的DOM操作通常是效能瓶頸產生的原因(如何進行高效能的複雜DOM操作通常是衡量一個前端開發人員技能的重要指標)。 React為此引進了虛擬DOM(Virtual DOM)的機制:在瀏覽器端用Javascript實作了一套DOM API。基於React進行開發時所有的DOM構造都是透過虛擬DOM進行,每當資料變化時,React都會重新建構整個DOM樹,然後React將目前整個DOM樹和上一次的DOM樹進行對比,得到DOM結構的區別,然後僅將需要變化的部分進行實際的瀏覽器DOM更新。而React能夠批次虛擬DOM的刷新,在一個事件循環(Event Loop)內的兩次資料變化會被合併,例如你連續的先將節點內容從A變成B,然後又從B變成A, React會認為UI不會發生任何變化,而如果透過手動控制,這種邏輯通常是極其複雜的。儘管每一次都需要建構完整的虛擬DOM樹,但是因為虛擬DOM是內存數據,性能是極高的,而對實際DOM進行操作的僅僅是Diff部分,因而能達到提高性能的目的。這樣,在確保效能的同時,開發者將不再需要關注某個資料的變化如何更新到一個或多個特定的DOM元素,而只需要關心在任意一個資料狀態下,整個介面是如何Render的。
如果你像在90年代那樣寫過伺服器端Render的純Web頁面那麼應該知道,伺服器端要做的就是根據資料Render出HTML送到瀏覽器端。如果這時因為使用者的一個點擊需要改變某個狀態文字,那麼也是透過刷新整個頁面來完成的。伺服器端並不需要知道是哪一小段HTML發生了變化,而只需要根據資料刷新整個頁面。換句話說,任何UI的變化都是透過整體刷新來完成的。而React將這種開發模式以高效能的方式帶到了前端,每做一點介面的更新,你都可以認為刷新了整個頁面。至於如何進行局部更新以確保效能,則是React框架要完成的事情。
借用Facebook介紹React的視訊中聊天應用程式的例子,當一條新的訊息過來時,傳統開發的思路如上圖,你的開發過程需要知道哪條數據過來了,如何將新的DOM結點添加到目前DOM樹上;而基於React的開發想法如下圖,你永遠只需要關心資料整體,兩次資料之間的UI如何變化,則完全交給框架去做。可以看到,使用React大大降低了邏輯複雜性,這意味著開發難度降低,可能產生Bug的機會也更少。