再次谈论React.js实现原生js拖拽效果引起的一系列问题


Posted in Javascript onApril 03, 2016

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的机会也更少。

Javascript 相关文章推荐
javascript 哈希表(hashtable)的简单实现
Jan 20 Javascript
boxy基于jquery的弹出层对话框插件扩展应用 弹出层选择器
Nov 21 Javascript
javascript中获取下个月一号,是星期几
Jun 01 Javascript
改进版通过Json对象实现深复制的方法
Oct 24 Javascript
js使用数组判断提交数据是否存在相同数据
Nov 27 Javascript
利用JS判断用户是否上网(连接网络)
Dec 23 Javascript
自动完成的搜索框javascript实现
Feb 26 Javascript
Javascript日期格式化format函数的使用方法
Aug 30 Javascript
jquery.masonry瀑布流效果
May 25 jQuery
Koa 中的错误处理解析
Apr 09 Javascript
jQuery实现日历效果
Sep 11 jQuery
nuxt 服务器渲染动态设置 title和seo关键字的操作
Nov 05 Javascript
jQuery qrcode生成二维码的方法
Apr 03 #Javascript
Node.js 应用跑得更快 10 个技巧
Apr 03 #Javascript
AngularJs 60分钟入门基础教程
Apr 03 #Javascript
深入浅析JSON.parse()、JSON.stringify()和eval()的作用详解
Apr 03 #Javascript
基于JavaScript实现 网页切出 网站title变化代码
Apr 03 #Javascript
BootStrap的弹出框(Popover)支持鼠标移到弹出层上弹窗层不隐藏的原因及解决办法
Apr 03 #Javascript
js一维数组、多维数组和对象的混合使用方法
Apr 03 #Javascript
You might like
德生1994机评
2021/03/02 无线电
PHP4实际应用经验篇(5)
2006/10/09 PHP
Mysql的Root密码忘记,查看或修改的解决方法(图文介绍)
2013/06/14 PHP
关于更改Zend Studio/Eclipse代码风格主题的介绍
2013/06/23 PHP
php 下载保存文件保存到本地的两种实现方法
2013/08/12 PHP
深入理解PHP变量的值类型和引用类型
2015/10/21 PHP
thinkphp ajaxfileupload实现异步上传图片的示例
2017/08/28 PHP
Laravel Intervention/image图片处理扩展包的安装、使用与可能遇到的坑详解
2017/11/14 PHP
JAVASCRIPT实现的WEB页面跳转以及页面间传值方法
2010/05/13 Javascript
JS 各种网页尺寸判断实例方法
2013/04/18 Javascript
选择复选框按钮置灰否则按钮可用
2014/05/22 Javascript
JavaScript语言对Unicode字符集的支持详解
2014/12/30 Javascript
js字符串截取函数slice、substring和substr的比较
2016/05/17 Javascript
有关JavaScript中call()和apply() 的一些理解
2016/05/20 Javascript
jQuery基本选择器之标签名选择器
2016/09/03 Javascript
浅谈jquery.form.js的ajaxSubmit和ajaxForm的使用
2016/09/09 Javascript
JS Canvas定时器模拟动态加载动画
2016/09/17 Javascript
微信小程序loading组件显示载入动画用法示例【附源码下载】
2017/12/09 Javascript
[01:25:09]2014 DOTA2国际邀请赛中国区预选赛 5 23 CIS VS DT第二场
2014/05/24 DOTA
[01:23:35]Ti4主赛事胜者组 DK vs EG 1
2014/07/19 DOTA
利用Python破解验证码实例详解
2016/12/08 Python
Python使用matplotlib实现的图像读取、切割裁剪功能示例
2018/04/28 Python
Python tkinter label 更新方法
2018/10/11 Python
Python3中列表list合并的四种方法
2019/04/19 Python
用Python画小女孩放风筝的示例
2019/11/23 Python
python中对二维列表中一维列表的调用方法
2020/06/07 Python
基于Python实现天天酷跑功能
2021/01/06 Python
美国网上鞋城:Shoeline.com
2016/11/17 全球购物
匡威意大利官方商店 :Converse意大利
2018/11/27 全球购物
医学院学生的自我评价分享
2013/11/19 职场文书
国际金融专业大学生职业生涯规划书
2013/12/28 职场文书
《和田的维吾尔》教学反思
2014/04/14 职场文书
客户经理竞聘演讲稿
2014/05/15 职场文书
2014年信访工作总结
2014/11/17 职场文书
2015年副班长工作总结
2015/05/15 职场文书
Java由浅入深通关抽象类与接口(上篇)
2022/04/26 Java/Android