react dnd 中文sortable 哪个好

这是可视化编辑器 Gaea-Editor 的第一篇连载分析文章,希望我能在有限的篇幅讲清楚制作这个网页编辑器的动机,以及可能带来的美好使用前景(画大饼)。它会具有如下几个特征:
运行在网页
文档流布局,绝对定位同时支持
对插入的任何 React 组件都可以直接作为编辑元素拖拽到页面中
兼容 React-Native 的 web 组件可以让它生成 android 和 ios 原生页面
拥有 Gaea-Preview 套件,传入 Gaea-Editor 生成的 json,可以立刻生成页面
拥有 Gaea-web-components Gaea-native-components 分别提供网页、原生基础最小粒度的组件
可以定制任何 React 组件插入到编辑器中
像 chrome-devtools 一样灵活,可以跨层级排序拖拽任何编辑区的元素
可以自定义组合模板,三下五除二搞定相似的需求
当然看完这篇文章,不仅限于了解这个编辑器的功能,我会非常详细介绍其设计细节,只要你仔细读它,完全可以做出自己的网页编辑器 ^_^。
在说这个可视化编辑器之前,不得不提到 React,这是我创作它的动机。虽然不确定 React 能火多久,但它带来的组件化掀起了一场前端界的工业革命,当然,组件化这个理念也不是 React 首创,但 React 大大降低了组件化的成本,就像发明了活字印刷术,让只有贵族才买得起的书本普及到了千家万户。
在全民组件化的时代里,我写过几篇文章介绍如何应用和管理组件 : 以及组件库的维护经验: 。现在组件化正在越来越普及,我们掌握了组件开发和管理的规律后,项目结构组织,团队间协作已经取得了飞速进步,组件化带来效率的提升也会日渐枯竭,但可视化编辑可能是一条突破瓶颈之路,第一,在有了现成组件的基础上,将其迁移到可视化编辑平台的成本非常小,第二,代码之外的页面开发更加直观,加上部分代码的辅佐会让结构组织更高效(类似 Unity 引擎)。
React 与原生拖拽结合
网页编辑器第一步,也是最重要的一步,就是拖拽功能了,我们希望最终效果如图所示:
如图所示,支持随意拖拽、拖拽动画,跨父级拖拽。我们使用 sortablejs 可以达到此效果,这篇文章重点就是介绍如何结合到 React。
使用 sortable.js
为了支持嵌套拖拽,我们使用开发版地址安装 &sortablejs&:&git:///RubaXa/Sortable.git#dev&
将 sortable 与 react 结合我们首先会想到在拖拽结束后重新 render,但这样做有如下几个缺点:
sortable 因为拖拽过程中改变了 dom 结构,所以操作流畅,但因此生成的 dom 节点脱离了 react 的控制
排序拖拽后会,sortable 会删除之前拖拽的节点,导致 react diff 算法删除元素时发现 dom 已经消失
总结来说就是既要让 sortable 操作 dom,又不能让 dom 操作导致脱离 react 的控制,我们采用操作回放的方式,将 sortable 操作结束后的 dom 修改回退,再将操作结果状态用 react 刷新。
右侧菜单配置
对右侧菜单配置如下:
Sortable.create(ReactDOM.findDOMNode(this.dragContainerInstance), {
// 放在一个组里,可以跨组拖拽
name: 'gaea-layout',
pull: 'clone',
put: false
sort: false,
onStart: (event: any) =& {
// 存储开始拖拽的位置和拖拽结束的位置
onEnd: (event: any) =& {
// 拖拽菜单时,真实元素会被拖拽走,拖拽成功的话元素会重复, 没成功拖拽会被添加到末尾
// 所以先移除 clone 的元素(吐槽下, 拖走的才是真的, 留下的才是 clone 的)
// 有 parentNode, 说明拖拽完毕还是没有被清除, 说明被拖走了, 因为如果没真正拖动成功, clone 元素会被删除
if (event.clone.parentNode) {
// 有 clone, 说明已经真正拖走了
this.dragContainerDomInstance.removeChild(event.clone)
// 再把真正移过去的弄回来
if (this.lastDragStartIndex === this.dragContainerDomInstance.childNodes.length) {
// 如果拖拽的是最后一个
this.dragContainerDomInstance.appendChild(event.item)
// 拖拽的不是最后一个
this.dragContainerDomInstance.insertBefore(event.item, this.dragContainerDomInstance.childNodes[this.lastDragStartIndex])
// 没拖走, 只是晃了一下, 不用管了
如上代码注释写的很详尽,解释一下就是,从菜单拖拽的配置要用 pull:clone 的方式配置,这样同一个元素才可以拖拽多次。put:false 让菜单不能被其它元素拖入。
当开始拖拽时,保存拖拽后的位置,便于找到用户拖拽的元素,在页面生成实例,同时保存拖拽前的位置,便于拖拽结束后恢复元素。
所以拖拽结束后,先判断 event.clone.parentNode,如果是空,说明元素并没有被拖走,所以不需要处理,否则需要先删除原先位置留下的 clone dom,因为这个元素不受 react 控制,再将真实拖走的元素还原到之前的位置
视图区域配置
编辑器视图区域的 sortable 配置比较长,因此拆解分析。
group 配置:
name: 'gaea-layout',
pull: true,
这个很容易理解,因为视图区域的元素可以被移走,也可以被其它元素移入,因此 pull 和 put 都是 true。
开始拖拽时
onStart: (event: any) =& {
// 保存拖拽前、后的位置
拖拽结束时
onEnd: (event: any) =& {
拖拽结束不需要做特殊处理,但可以做一些视觉设置,比如告诉用户拖拽结束了。
有元素新增时
onAdd: (event: any)=& {
// 取消 srotable 对 dom 的修改
// 删掉 dom 元素, 让 react 去生成 dom
if (this.props.viewport.currentMovingComponent.isNew) {
// 是新拖进来的, 不用管, 因为工具栏会把它收回去
// 为什么不删掉? 因为这个元素不论是不是 clone, 都被移过来了, 不还回去 react 在更新 dom 时会无法找到
// 如果是从某个元素移过来的(新增的,而不是同一个父级改变排序)
// 把这个元素还给之前拖拽的父级
if (this.props.viewport.dragStartParentElement.childNodes.length === 0) {
// 之前只有一个元素
this.props.viewport.dragStartParentElement.appendChild(event.item)
} else if (this.props.viewport.dragStartParentElement.childNodes.length === this.props.viewport.dragStartIndex) {
// 是上一次位置是最后一个, 而且父元素有多个元素
this.props.viewport.dragStartParentElement.appendChild(event.item)
// 不是最后一个, 而且有多个元素
// 插入到它下一个元素的前一个
this.props.viewport.dragStartParentElement.insertBefore(event.item, this.props.viewport.dragStartParentElement.childNodes[this.props.viewport.dragStartIndex])
有元素新增后,有两种情况:新增元素,或者从已有元素中拖拽进来新增。
如果是从工具栏拖拽进来新增的元素,只需要用 react 重新渲染一遍即可。
如果是从其它视图元素中移入进来的,需要把这个元素还原到之前拖拽的位置,这样就回退到 sortable 操作之前的状态,再用 react 渲染这两个父级组件。
同一父级内元素位置更新时
onUpdate: (event: any)=& {
// 同一个父级下子元素交换父级
// 取消 srotable 对 dom 的修改, 让元素回到最初的位置即可复原
const oldIndex = event.oldIndex as number
const newIndex = event.newIndex as number
if (this.props.viewport.dragStartParentElement.childNodes.length === oldIndex + 1) {
// 是从最后一个元素开始拖拽的
this.props.viewport.dragStartParentElement.appendChild(event.item)
if (newIndex & oldIndex) {
// 如果移到了后面
this.props.viewport.dragStartParentElement.insertBefore(event.item, this.props.viewport.dragStartParentElement.childNodes[oldIndex])
// 如果移到了前面
this.props.viewport.dragStartParentElement.insertBefore(event.item, this.props.viewport.dragStartParentElement.childNodes[oldIndex + 1])
this.props.viewport.sortComponents(this.props.mapUniqueKey, event.oldIndex as number, event.newIndex as number)
我们只需要对元素位置进行还原,之后根据起点位置和终点位置模拟元素移动,再使用 react 渲染即可。这里需要注意, sortable 的拖拽不是简单的a b互换,而是 a -& b,下面用图简单描述一下:
如上图所示,同一个父级下有6个元素,当我们拖拽第一个元素到第5个元素时,排序不是变成了 5 2 3 4 1 6,而是如下图所示:
不可避免的产生了互换,我们逐一互换元素位置,然后更新父级元素子元素的位置。注意此时最佳状态是不触发 react 元素渲染,我们只要保证子元素的 key 不变, react diff 算法会自动移动 dom 节点,而不是重新渲染 1 2 3 4 5 这 5 个子节点。
当元素被移走时
onRemove: (event: any)=& {
// 渲染父级元素,减少一个子元素在当前位置
当元素被移走时,不会触发 onUpdate 方法,而会触发 onAdd 方法,但是我们已经在 onAdd 方法中将移走的元素还原回去,因此这里不需要做任何处理,相当于没有改动,我们只需要更新 react 父级元素重新渲染,让 react 将元素移走即可。
基于以上菜单区域和视图区域的博弈,终于将 sortable 与 react 渲染完美结合起来,然而不用担心有什么副作用,因为我们已经将所有 sortable 的操作还原,所以实际上只用了它的拖拽过程已经拖拽结果,忙到后来其实没有改变任何 dom 结构,最终 dom 元素的变化还是由 react 来控制。
后续系列我们会继续剖析实现部分,以及放上仓库地址。解析到底是如何将元素放在视图区域,并且并支持无限层级嵌套的,敬请期待!
阅读(...) 评论()Sortable Grid View on react-native - Stack Overflow
to customize your list.
Join the Stack Overflow Community
Stack Overflow is a community of 6.7 million programmers, just like you, helping each other.
J it only takes a minute:
I'm trying to create a sortable grid view on react native like you can have on your ios or android system. So far I have created a grid with a ListView and Flexbox but I have tried several solution to make it sortable but nothing works. So I was wondering if you know anyother solution to create that grid.
closed as off-topic by , , , ,
This question appears to be off-topic. The users who voted to close gave this specific reason:&Questions asking us to recommend or find a book, tool, software library, tutorial or other off-site resource are off-topic for Stack Overflow as they tend to attract opinionated answers and spam. Instead,
and what has been done so far to solve it.& & Machavity, Baum mit Augen, Jan Dvorak, Sotirios Delimanolis, Tiny GiantIf this question can be reworded to fit the rules in the , please .
I decided to create a component called widget and make it draggable thanks to . So instead of having a ListView I have a component called Grid and then inside that component I created others called Widgets which have a drag and drop animation.
I still use flexbox to order that components in a proper way on the grid component but I decided to change from the ListView to a separate component because I have more flexibility to change the properties and styles of the widget components.
Not the answer you're looking for?
Browse other questions tagged
rev .25058
Stack Overflow works best with JavaScript enabled}

我要回帖

更多关于 react好还是vuejs好 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信