精读《结合 React 使用原生 Drag Drop API》
2020/2/24 11:02:52
本文主要是介绍精读《结合 React 使用原生 Drag Drop API》,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
1 引言
拖拽是前端非常常见的交互操作,但显然拖拽是强 DOM 交互的,而 React 绕过了 DOM 这一层,那么基于 React 的拖拽方案就必定值得聊一聊。
结合 How To Use The HTML Drag-And-Drop API In React 这篇文章,让我们谈谈 React 拖拽这些事。
2 概述
原文说的比较简单,笔者先快速介绍其中重点部分。
首先拖拽主要的 API 有 4 个:dragEnter
dragLeave
dragOver
drop
,分别对应拖入、拖出、正在当前元素范围内拖拽、完成拖入动作。
基于这些 API,我们可以利用 React 实现一个拖入区域:
import React from "react"; const DragAndDrop = props => { const handleDragEnter = e => { e.preventDefault(); e.stopPropagation(); }; const handleDragLeave = e => { e.preventDefault(); e.stopPropagation(); }; const handleDragOver = e => { e.preventDefault(); e.stopPropagation(); }; const handleDrop = e => { e.preventDefault(); e.stopPropagation(); }; return ( <div className={"drag-drop-zone"} onDrop={e => handleDrop(e)} onDragOver={e => handleDragOver(e)} onDragEnter={e => handleDragEnter(e)} onDragLeave={e => handleDragLeave(e)} > <p>Drag files here to upload</p> </div> ); }; export default DragAndDrop; 复制代码
preventDefault
指的是阻止默认响应,这个响应可能是跳转页面之类的,stopPropagation
是阻止冒泡,这样同样监听了事件的父元素就不会收到响应,我们可以精准作用于嵌套的子元素。
接下来是拖拽状态管理,提到了 useReducer
,顺便复习一下用法:
... const reducer = (state, action) => { switch (action.type) { case 'SET_DROP_DEPTH': return { ...state, dropDepth: action.dropDepth } case 'SET_IN_DROP_ZONE': return { ...state, inDropZone: action.inDropZone }; case 'ADD_FILE_TO_LIST': return { ...state, fileList: state.fileList.concat(action.files) }; default: return state; } }; const [data, dispatch] = React.useReducer( reducer, { dropDepth: 0, inDropZone: false, fileList: [] } ) ... 复制代码
最后一个关键点在于拖入后的处理,利用 dispatch
增加拖入文件、设置拖入状态即可:
const handleDrop = e => { ... let files = [...e.dataTransfer.files]; if (files && files.length > 0) { const existingFiles = data.fileList.map(f => f.name) files = files.filter(f => !existingFiles.includes(f.name)) dispatch({ type: 'ADD_FILE_TO_LIST', files }); e.dataTransfer.clearData(); dispatch({ type: 'SET_DROP_DEPTH', dropDepth: 0 }); dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: false }); } }; 复制代码
e.dataTransfer.clearData
函数用于清除拖拽过程中产生的临时变量,这些临时变量可以通过 e.dataTransfer.xxx =
的方式赋值,一般用于拖拽过程中值的传递。
总结一下,利用 HTML5 的 API 将拖拽转化为状态,最终通过状态映射到 UI。
原文内容还是比较简单的,笔者在精读部分再拓展一些更体系化的内容。
3 精读
现阶段拖拽主要分为两种,一种是 HTML5 原生规范的拖拽,这种方式在拖拽过程中不会影响 DOM 结构。另一种是完全所见即所得的拖拽方式,拖拽过程中 DOM 位置会随之变动,好处是可以立即反馈拖拽结果,当然缺点是华而不实,一旦用在生产环境,这种拖拽过程可能导致页面结构频繁跳动,反而看不清拖拽效果。
由于本文也采用了第一种拖拽方案,因为笔者再重新整理一遍自己的封装思路。
从使用角度反推,假设我们拥有一个拖拽库,那必定要拥有两个 API:
import { DragContainer, DropContainer } from 'dnd' const DragItem = ( <DragContainer> {({ dragProps }) => ( <div {...dragProps} /> )} </DragContainer> ) const DropItem = ( <DropContainer> {({ dropProps }) => ( <div {...dropProps} /> )} </DropContainer> ) 复制代码
DragContainer
包裹可以被拖拽的元素,DragContainer
包裹可以被拖入的元素,而至于 dragProps
与 dropProps
需要透传到子元素的 dom 节点,是为了利用 DOM API 控制拖拽效果,这也是拖拽唯一对 DOM 的要求,双方元素都需要有实体 DOM 承载。
而上面例子中给出 dragProps
与 dropProps
的方式属于 RenderProps,我们可以将 children
当作函数执行以达到效果:
const DragContainer = ({ children, componentId }) => { const { dragProps } = useDnd(componentId) return children({ dragProps }) } const DropContainer = ({ children, componentId }) => { const { dropProps } = useDnd(componentId) return children({ dropProps }) } 复制代码
那么这里创建了一个自定义 Hook useDnd
接收 dragProps
与 dropProps
,这个自定义 Hook 可以这么写:
const useDnd = ({ componentId }) => { const dragProps = {} const dropProps = {} return { dragProps, dropProps } } 复制代码
接下来,我们就要分别实现 drag
与 drop
了。
对 drag
来说,只要实现 onDragStart
与 onDragEnd
即可:
const dragProps = { onDragStart: ev => { ev.stopPropagation() ev.dataTransfer.setData('componentId', componentId) }, onDragEnd: ev => { // 做一些拖拽结束的清理工作 } } 复制代码
stopPropagation
的作用在原文简介中已经介绍过了,setData
则是通知拖拽方,当前拖拽的组件 id 是什么,这是由于拖拽由 drag
发起而由 drop
响应,因此必须有个数据传输过程,而 dataTransfer
就最适合做这件事。
对于 drop
来说,只要实现 onDragOver
与 onDrop
即可:
const dropProps = { onDropOver: ev => { // 做一些样式处理,提示用户此时松手会将元素防止在何处 }, onDrop: ev => { ev.stopPropagation() const componentId = ev.dataTransfer.getData('componentId') // 通过 componentId 修改数据,通过 React Rerender 刷新 UI } } 复制代码
重点在 onDrop
,它是实现拖拽效果的 “真正执行处”,最终通过修改 UI 的方式更新数据。
存在一种场景,一个容器既可以被拖动,也可以被拖入,这种情况一般这个组件是个容器,但这个容器可以被拖入到其他容器中,可以自由嵌套。
实现这种场景的方式就是将 DragContainer
与 DropContainer
作用到一个组件上:
const Box = ( <DragContainer> {({ dragProps }) => ( <DropContainer> {({ dropProps }) => { <div {...dragProps} {...dropProps} /> }} </DropContainer> )} </DragContainer> ) 复制代码
之所以能嵌套,在于 HTML5 的 API 允许一个元素同时拥有 onDragStart
、onDrop
这两种属性,而上面的语法不过是同时将这两种属性传给组件 DOM。
所以,动手实现一个拖拽库就是这么简单,只要活用 HTML5 的拖拽 API,结合 React 一些特殊语法便够了。
4 总结
最后留下一个思考题,许多具有拖拽功能的系统都具备 “拖拽 placeholder” 的功能,即拖拽元素的过程中,在其 “落点” 位置展示一条横线或竖线,引导出松手后元素位置落点,如图所示:
那么这条辅助线是通过什么方式实现的呢?欢迎在评论区留言!如果你有辅助线实现方案解析的文章,欢迎分享,也可以期待笔者未来专门写一篇 “拖拽 placeholder” 实现剖析的精读。
如果你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。
关注 前端精读微信公众号
版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证)
这篇关于精读《结合 React 使用原生 Drag Drop API》的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-24Vue CLI多环境配置学习:从入门到实践
- 2024-11-24Vue CLI多环境配置学习:新手入门教程
- 2024-11-24Vue CLI学习:初学者指南
- 2024-11-24Vue CLI学习:从入门到上手的简单教程
- 2024-11-24Vue3+Vite学习:从零开始的前端开发之旅
- 2024-11-24Vue3阿里系UI组件学习入门教程
- 2024-11-24Vue3的阿里系UI组件学习入门指南
- 2024-11-24Vue3公共组件学习:新手入门教程
- 2024-11-24Vue3公共组件学习入门指南
- 2024-11-24vue3核心功能响应式变量学习