Hooks + TS 搭建一个任务管理系统(八)-- 拖拽功能实现
📢 大家好,我是小丞同学,一名大二的前端爱好者
📢 这个系列文章是实战 jira 任务管理系统的一个学习总结
📢 非常感谢你的阅读,不对的地方欢迎指正 🙏
📢 愿你忠于自己,热爱生活
在上一篇文章中,我们写好了任务组页面,就现在来说我们的项目已经基本完成了,所有的 CRUD 操作、路由跳转、页面布局都已经实现了。在这一篇文章中,我们再来优化一下我们的项目,我们给我的看板页面添加一个拖拽功能
这篇内容不是很懂,有点水,弄懂再来改
💡 知识点抢先看
- 给看板添加拖拽功能
- 讲解 HTML5 中的
drop
和drag
一、给看板添加拖拽功能
这一篇文章就只讲一个部分,正如标题所说,添加一个拖拽功能
实现效果像这样
我们实现这个功能采用了一个 react-beautiful-dnd
的库,关于这个库可以查看 : npm官网
关于这个库的使用呢,我们简单的介绍一下,首先我们需要定义一个 Droppable
组件来包裹我们的拖拽的元素,表示这块区域的内容我们能够拖拽,其次需要对放的地方,也就是我们的元素添加一个 Draggable
组件包裹,用来表示这块区域是能够放下的区域
在这里是重写了自带的 Drop
和 Drag
组件
这部分比较难,搞得不是很懂,提几个点吧
- 在这里我们想要抽离出一个
children
属性,不使用原生的children
属性 - 由于 API 的要求,我们需要预留接收
ref
,这里我们采用转发的方式来实现,通过forwardRef
的方式来实现
export const DropChild = React.forwardRef<HTMLDivElement, DropChildProps>(({ children, ...props }, ref) =>
<div ref={ref} {...props}>
{children}
{/* api要求加的 */}
{props.provided?.placeholder}
</div>
)
1. 实现 Drop
组件
// 这个文件相当于重构了 drop 原生组件
// 定义一个类型,不想用 自带的 children ,采用自己的
type DropProps = Omit<DroppableProps, 'children'> & { children: ReactNode }
export const Drop = ({ children, ...props }: DropProps) => {
return <Droppable {...props}>
{
(provided => {
if (React.isValidElement(children)) {
// 给所有的子元素都加上props属性
return React.cloneElement(children, {
...provided.droppableProps,
ref: provided.innerRef,
provided
})
}
return <div />
})
}
</Droppable>
}
2. 实现 Drag
组件
type DragProps = Omit<DraggableProps, 'children'> & { children: ReactNode }
export const Drag = ({ children, ...props }: DragProps) => {
return <Draggable {...props}>
{
provided => {
if (React.isValidElement(children)) {
return React.cloneElement(children, {
...provided.draggableProps,
...provided.dragHandleProps,
ref: provided.innerRef
})
}
return <div />
}
}
</Draggable>
}
3. 拖拽持久化
写好了两个组件,虽然很难,可以直接 cv
一下这部分的代码。
- 理解起来还是挺可以的,使用
Drop
组件包裹拖的位置,用Drag
组件包裹放的位置 - 最后我们需要持久化我们的状态,这里采用的是原生组件中自带的
onDragEnd
方法来实现
我们在这里需要再实现一个 hook
来实现这个功能,很难
这里我们通过 if
判断它当前是拖的看板还是任务,判断一下是左右还是上下拖拽,通过组件中自带的方法计算出放下的 id
和拿起来的 id
将它插入到这个 kanban
任务中即可
当我们拖拽完成时,会返回
source
和destination
对象,这里面有我们拖拽的相关信息
如果是 column
的话就是看板之间的拖拽,我们需要调用我们新封装的一个 useReorderKanban
方法进行持久化
如果是 row
则调用任务之间的持久化方法 useRecordTask
方法进行持久化
export const useDragEnd = () => {
// 先取到看板
const { data: kanbans } = useKanbans(useKanbanSearchParams())
const { mutate: reorderKanban } = useReorderKanban(useKanbansQueryKey())
// 获取task信息
const { data: allTasks = []} = useTasks(useTasksSearchParams())
const { mutate: reorderTask } = useReorderTask(useTasksQueryKey())
return useCallback(({ source, destination, type }: DropResult) => {
if (!destination) {
return
}
// 看板排序
if (type === 'COLUMN') {
const fromId = kanbans?.[source.index].id
const toId = kanbans?.[destination.index].id
// 如果没变化的时候直接return
if (!fromId || !toId || fromId === toId) {
return
}
// 判断放下的位置在目标的什么方位
const type = destination.index > source.index ? 'after' : 'before'
reorderKanban({ fromId, referenceId: toId, type })
}
if (type === 'ROW') {
// 通过 + 转变为数字
const fromKanbanId = +source.droppableId
const toKanbanId = +destination.droppableId
// 不允许跨版排序
if (fromKanbanId !== toKanbanId) {
return
}
// 获取拖拽的元素
const fromTask = allTasks.filter(task => task.kanbanId === fromKanbanId)[source.index]
const toTask = allTasks.filter(task => task.kanbanId === fromKanbanId)[destination.index]
//
if (fromTask?.id === toTask?.id) {
return
}
reorderTask({
fromId: fromTask?.id,
referenceId: toTask?.id,
fromKanbanId,
toKanbanId,
type: fromKanbanId === toKanbanId && destination.index > source.index ? 'after' : 'before'
})
}
}, [allTasks, kanbans, reorderKanban, reorderTask])
}
4. useReorderKanban
通过传入一组数据,包括起始位置,插入位置,在插入位置的前面还是后面,这些数据,进行后台接口的判断,来进行持久化,这里采用的 useMutation
就是前面讲的,使用方法都很熟练了
// 持久化数据接口
export const useReorderKanban = (queryKey:QueryKey) => {
const client = useHttp()
return useMutation(
(params: SortProps) => {
return client('kanbans/reorder', {
data: params,
method: "POST"
})
},
useReorderKanbanConfig(queryKey)
)
}
5. 在 HTML5 中新增的 Drop 和 Drag
当我们需要设置某个元素可拖放时,只需要 draggable
设置为 true
<img draggable="true">
当拖放执行时,会发生 ondragstart
和 setData()
执行 ondragstart
会调用一个函数 drag
函数,它规定了被拖拽的数据
function drag(event)
{
event.dataTransfer.setData("Text",ev.target.id);
}
这里的
Text
是我们需要添加到drag object
中的数据类型
在何处放置被拖动的数据
默认地,无法将数据/元素放置到其他元素中。如果需要设置允许放置,我们必须阻止对元素的默认处理方式。
这要通过调用
ondragover
事件的event.preventDefault()
方法:
event.preventDefault()
当防止时会发生 drop
事件
function drop(ev)
{
ev.preventDefault();
var data=ev.dataTransfer.getData("Text");
ev.target.appendChild(document.getElementById(data));
}
代码解释:
- 调用
preventDefault()
来避免浏览器对数据的默认处理(drop
事件的默认行为是以链接形式打开) - 通过
dataTransfer.getData("Text")
方法获得被拖的数据。该方法将返回在setData()
方法中设置为相同类型的任何数据。 - 被拖数据是被拖元素的
id ("drag1")
- 把被拖元素追加到放置元素(目标元素)中
(参考于菜鸟教程)
可以亲自试一试:在线演示
📌 总结
- 大概了解了一下如何使用
react-beautiful-dnd
- 关于拖拽持久化有了大概的认识
- 了解了 HTML5 中的
drop
和drag
最后,可能在很多地方讲述的不够清晰,请见谅
💌 如果文章有什么错误的地方,或者有什么疑问,欢迎留言,也欢迎私信交流
- 点赞
- 收藏
- 关注作者
评论(0)