再次谈论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 相关文章推荐
一个JS翻页效果
Jul 23 Javascript
JS 控制CSS样式表
Aug 20 Javascript
JavaScript 学习笔记之一jQuery写法图片等比缩放以及预加载
Jun 28 Javascript
jQuery自动切换/点击切换选项卡效果的小例子
Aug 12 Javascript
鼠标移到div,浮层显示明细,弹出层与div的上边距左边距重合(示例代码)
Dec 14 Javascript
JavaScript高仿支付宝倒计时页面及代码实现
Oct 21 Javascript
jQuery下拉菜单的实现代码
Nov 03 Javascript
vue2.0与bootstrap3实现列表分页效果
Nov 28 Javascript
vue中v-for加载本地静态图片方法
Mar 03 Javascript
小程序click-scroll组件设计
Jun 18 Javascript
基于layPage插件实现两种分页方式浅析
Jul 27 Javascript
axios如何利用promise无痛刷新token的实现方法
Aug 27 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
如何跨站抓取别的站点的页面的补充
2006/10/09 PHP
php类中private属性继承问题分析
2012/11/01 PHP
php写入数据到CSV文件的方法
2015/03/14 PHP
php7 安装yar 生成docker镜像
2017/05/09 PHP
用javascript添加控件自定义属性解析
2013/11/25 Javascript
为开发者准备的10款最好的jQuery日历插件
2014/02/04 Javascript
javascript操作excel生成报表示例
2014/05/08 Javascript
avalonjs实现仿微博的图片拖动特效
2015/05/06 Javascript
微信小程序商城项目之侧栏分类效果(1)
2017/04/17 Javascript
纯JS实现只能输入数字的简单代码
2017/06/21 Javascript
关于JavaScript中的this指向问题总结篇
2017/07/23 Javascript
jQuery实现腾讯信用界面(自制刻度尺)样式
2017/08/15 jQuery
Vue结合SignalR实现前后端实时消息同步
2017/09/19 Javascript
Angular2学习笔记之数据绑定的示例代码
2018/01/03 Javascript
Vue 2.5.2下axios + express 本地请求404的解决方法
2018/02/21 Javascript
纯JS实现的读取excel文件内容功能示例【支持所有浏览器】
2018/06/23 Javascript
jQuery实现简单的Ajax调用功能示例
2019/02/15 jQuery
Vue2.0实现组件之间数据交互和通信操作示例
2019/05/16 Javascript
Python3中多线程编程的队列运作示例
2015/04/16 Python
django上传图片并生成缩略图方法示例
2017/12/11 Python
Flask之flask-session的具体使用
2018/07/26 Python
解决Python找不到ssl模块问题 No module named _ssl的方法
2019/04/29 Python
django之状态保持-使用redis存储session的例子
2019/07/28 Python
Python Pillow.Image 图像保存和参数选择方式
2020/01/09 Python
基于python实现复制文件并重命名
2020/09/16 Python
Python浮点型(float)运算结果不正确的解决方案
2020/09/22 Python
儿科护理实习自我鉴定
2013/09/19 职场文书
新年主持词
2014/03/27 职场文书
授权委托书(完整版)
2014/09/10 职场文书
学校领导班子成员查摆问题及整改措施
2014/10/28 职场文书
2016年寒假社会实践活动总结
2015/03/27 职场文书
吧主申请感言怎么写
2015/08/03 职场文书
学习委员竞选稿
2015/11/20 职场文书
领导干部学习十八届五中全会精神心得体会
2016/01/05 职场文书
《我是什么》教学反思
2016/02/16 职场文书
JS轻量级函数式编程实现XDM三
2022/06/16 Javascript