React 起源于 Facebook 的內(nèi)部項(xiàng)目,因?yàn)樵摴緦κ袌錾纤?JavaScript MVC 框架,都不滿意,就決定自己寫一套,用來架設(shè) Instagram 的網(wǎng)站。做出來以后,發(fā)現(xiàn)這套東西很好用,就在2013年5月開源了。由于 React 的設(shè)計(jì)思想極其獨(dú)特,屬于革命性創(chuàng)新,性能出眾,代碼邏輯卻非常簡單。所以,越來越多的人開始關(guān)注和使用,認(rèn)為它可能是將來 Web 開發(fā)的主流工具。
前幾天寫的那個拖拽,自己留下的疑問。。。這次在熱心博友的提示下又修正了一些小小的bug,也加了拖拽的邊緣檢測部分。。。就再聊聊拖拽吧
一、不要直接操作dom元素
react中使用了虛擬dom的概念,目地就是要盡量避免直接操作dom元素,所以我們在對dom元素進(jìn)行操作的時(shí)候需要注意,我之前為了獲取form的參數(shù)就直接用了var dragBox=document.getElementById('form')去找dom,但是其實(shí)記錄from的初始位置,可以在其子組件更新父組件參數(shù)的時(shí)候調(diào)用。即在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);
},
這樣就可以直接在父組件中操作自己,而不是在子組件中調(diào)用。
二、onmousemove和onmouseup事件應(yīng)該綁定到document上
拖拽事件中,當(dāng)鼠標(biāo)在DragArea中按下后,就應(yīng)該檢測鼠標(biāo)在document中移動的距離及何時(shí)彈起。否則直接綁定在form的話會有一個不雅的地方,就是拖動條拖動邊緣附近的時(shí)候,如果鼠標(biāo)速度快一點(diǎn)會失效,鼠標(biāo)再回來拖動條會自動吸上鼠標(biāo)。因此利用react初始化階段的componentDidMount函數(shù),這個函數(shù)是組件被裝載后才會被調(diào)用,也就是說調(diào)用這個方法的時(shí)候,組件已經(jīng)被渲染到了頁面上,這個時(shí)候可以修改DOM。也就是說此時(shí)把相應(yīng)事件再綁定到document上面,如下代碼:
componentDidMount:function(){
document.addEventListener('mousemove',(e)=>{this.move(e);},false);/*ES6新特性,箭頭函數(shù),需要依賴jsx編譯工具才能正確運(yùn)行*/
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){//檢測屏幕左邊,因?yàn)槲疫@里的初始居中是利用了負(fù)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){ //檢測屏幕上邊,因?yàn)槲疫@里的初始居中是利用了負(fù)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:新的代碼已經(jīng)更新在我的github上面,大家可以研究一下。
ReactJS的背景和原理
在Web開發(fā)中,我們總需要將變化的數(shù)據(jù)實(shí)時(shí)反應(yīng)到UI上,這時(shí)就需要對DOM進(jìn)行操作。而復(fù)雜或頻繁的DOM操作通常是性能瓶頸產(chǎn)生的原因(如何進(jìn)行高性能的復(fù)雜DOM操作通常是衡量一個前端開發(fā)人員技能的重要指標(biāo))。React為此引入了虛擬DOM(Virtual DOM)的機(jī)制:在瀏覽器端用Javascript實(shí)現(xiàn)了一套DOM API?;赗eact進(jìn)行開發(fā)時(shí)所有的DOM構(gòu)造都是通過虛擬DOM進(jìn)行,每當(dāng)數(shù)據(jù)變化時(shí),React都會重新構(gòu)建整個DOM樹,然后React將當(dāng)前整個DOM樹和上一次的DOM樹進(jìn)行對比,得到DOM結(jié)構(gòu)的區(qū)別,然后僅僅將需要變化的部分進(jìn)行實(shí)際的瀏覽器DOM更新。而且React能夠批處理虛擬DOM的刷新,在一個事件循環(huán)(Event Loop)內(nèi)的兩次數(shù)據(jù)變化會被合并,例如你連續(xù)的先將節(jié)點(diǎn)內(nèi)容從A變成B,然后又從B變成A,React會認(rèn)為UI不發(fā)生任何變化,而如果通過手動控制,這種邏輯通常是極其復(fù)雜的。盡管每一次都需要構(gòu)造完整的虛擬DOM樹,但是因?yàn)樘摂MDOM是內(nèi)存數(shù)據(jù),性能是極高的,而對實(shí)際DOM進(jìn)行操作的僅僅是Diff部分,因而能達(dá)到提高性能的目的。這樣,在保證性能的同時(shí),開發(fā)者將不再需要關(guān)注某個數(shù)據(jù)的變化如何更新到一個或多個具體的DOM元素,而只需要關(guān)心在任意一個數(shù)據(jù)狀態(tài)下,整個界面是如何Render的。
如果你像在90年代那樣寫過服務(wù)器端Render的純Web頁面那么應(yīng)該知道,服務(wù)器端所要做的就是根據(jù)數(shù)據(jù)Render出HTML送到瀏覽器端。如果這時(shí)因?yàn)橛脩舻囊粋€點(diǎn)擊需要改變某個狀態(tài)文字,那么也是通過刷新整個頁面來完成的。服務(wù)器端并不需要知道是哪一小段HTML發(fā)生了變化,而只需要根據(jù)數(shù)據(jù)刷新整個頁面。換句話說,任何UI的變化都是通過整體刷新來完成的。而React將這種開發(fā)模式以高性能的方式帶到了前端,每做一點(diǎn)界面的更新,你都可以認(rèn)為刷新了整個頁面。至于如何進(jìn)行局部更新以保證性能,則是React框架要完成的事情。
借用Facebook介紹React的視頻中聊天應(yīng)用的例子,當(dāng)一條新的消息過來時(shí),傳統(tǒng)開發(fā)的思路如上圖,你的開發(fā)過程需要知道哪條數(shù)據(jù)過來了,如何將新的DOM結(jié)點(diǎn)添加到當(dāng)前DOM樹上;而基于React的開發(fā)思路如下圖,你永遠(yuǎn)只需要關(guān)心數(shù)據(jù)整體,兩次數(shù)據(jù)之間的UI如何變化,則完全交給框架去做??梢钥吹?,使用React大大降低了邏輯復(fù)雜性,意味著開發(fā)難度降低,可能產(chǎn)生Bug的機(jī)會也更少。