react拖拽库dnd-kit
安装拖拽库:
npm i @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
里面有一些组件:
-
DndContext : 拖拽功能最外面的容器
- onDragEnd : 拖拽完成之后的回调函数
- collisionDetection : 碰撞函数
- sensors : 定义传感器,默认的传感器只有PointerSensor,,如果想要监听键盘,或者手机端的touch事件,就需要配置别的传感器
-
SortableContext: 告诉dnd-kit,哪一个容器是可以被拖拽和放置的区域
- items :表示dnd-kit要跟踪哪些项
- strategy : 排序策略
-
const {attributes,listeners,setNodeRef,transform,transition} = useSortable({id});
setNodeRef
: 绑定每个拖拽的盒子
import React, {PropsWithChildren, useState} from 'react';
import logo from './logo.svg';
import './App.css';
import {
closestCorners,
DndContext,
DragEndEvent, KeyboardSensor, PointerSensor, TouchSensor,
useDraggable,
useDroppable,
useSensor,
useSensors
} from "@dnd-kit/core";
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
useSortable,
verticalListSortingStrategy
} from "@dnd-kit/sortable";
import {CSS} from "@dnd-kit/utilities"
interface taskType {
id:number,
title:string
}
function Task({id,title}:taskType){
// 告诉 dnd-kit 这个是拖拽元素
const {attributes,listeners,setNodeRef,transform,transition} = useSortable({id});
const style = {
transition,
transform:CSS.Transform.toString(transform)
}
// dnd-kit控制元素外观的方式,,是创建一个style对象
return (
// 给盒子添加引用,,dnd-kit可以跟踪他
<div className="task" ref={setNodeRef} {...attributes} {...listeners} style={style}>
<input type="checkbox" className="checkbox" />
{title}
</div>
)
}
type propType = {
tasks:Array<taskType>
}
function Column({tasks}:propType){
return (
<div>
{/* 告诉 dnd-kit 哪一个容器是可以被拖动和放置的区域 用SortableContext包裹===> items表示要跟踪哪些项 ===> strategy:排序策略*/}
<SortableContext items={tasks} strategy={verticalListSortingStrategy}>
{
tasks.map(item=>(<Task id={item.id} title={item.title} key={item.id} />))
}
</SortableContext>
</div>
)
}
type InputProp={
onSubmit:(val:string)=>void
}
function Input({onSubmit}:InputProp){
const [inputValue, setInputValue] = useState("")
function handleSubmit(){
if (!inputValue){
return
}
onSubmit(inputValue)
setInputValue("")
}
return (
<div className="py-2 flex items-center justify-between">
<input type="text" value={inputValue}
placeholder="请输入task"
onChange={(e)=>setInputValue(e.target.value)}/>
<button onClick={handleSubmit} className="px-2 rounded bg-yellow-200">添加</button>
</div>
)
}
function App() {
const [tasks, setTasks] = useState([
{id:1,title:"hehe"},
{id:2,title:"222"},
{id:3,title:"333"},
])
// 其他传感器,比如手机的touch,,键盘按键
const sensors = useSensors(
// 指针传感器
useSensor(PointerSensor),// 默认的senser
useSensor(TouchSensor),
useSensor(KeyboardSensor,{// enter+ 上下键
coordinateGetter: sortableKeyboardCoordinates
})
)
const [inputValue, setInputValue] = useState("")
const addTask = (title:string)=>{
setTasks(prevTasks=>{
return [...prevTasks,{id:prevTasks.length+2,title: title}]
})
}
return (
<div>
<Input onSubmit={addTask} />
<DndContext collisionDetection={closestCorners} onDragEnd={handleDragEnd} sensors={sensors}>
<Column tasks={tasks}/>
</DndContext>
</div>
);
function handleDragEnd(event: DragEndEvent) {
const {active, over} = event
if (active.id === over?.id) return
setTasks(prevTasks => {
var activePos = prevTasks.findIndex(task=>task.id===active.id);
var newPos = prevTasks.findIndex(task=>task.id===over?.id);
return arrayMove(prevTasks,activePos,newPos)
})
}
}
export default App;
封装拖拽组件,将拖拽的元素动态传入
import {JSX} from "react";
import {
closestCenter,
DndContext,
DragEndEvent,
KeyboardSensor,
MouseSensor,
PointerSensor,
useSensor,
useSensors
} from "@dnd-kit/core";
import {SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy} from "@dnd-kit/sortable";
type PropsType = {
children: JSX.Element | JSX.Element[], // 传入子组件,,拖拽的组件
items: Array<{id:string,[key:string]:any}>, // 扩展key是任何string的属性
onDragEnd:(oldIndex:number,newIndex:number)=>void // drag的回调,,传入组件不同,拖拽回调也不同
}
function SortableContainer(props:PropsType){
const {children,items,onDragEnd} = props
const sensors = useSensors(
// useSensor(PointerSensor),
// useSensor(KeyboardSensor,{
// coordinateGetter:sortableKeyboardCoordinates
// }),
useSensor(MouseSensor,{
activationConstraint:{
distance:8 // 8px,,移动超过8px才算拖拽
}
})
)
function handleDragEnd(event:DragEndEvent){
// const {} = event
const {active,over} = event
if (over == null){
return
}
if (active.id === over.id){
return;
}
var oldIndex = items.findIndex(c=>c.id===active.id);
var newIndex = items.findIndex(c=>c.id===over.id);
onDragEnd(oldIndex,newIndex)
}
return (
<div>
<DndContext onDragEnd={handleDragEnd} sensors={sensors} collisionDetection={closestCenter}>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
{/* 怎么循环不管,,,直接引入子组件*/}
{children}
</SortableContext>
</DndContext>
</div>
)
}
export default SortableContainer
import {useSortable} from "@dnd-kit/sortable";
import {JSX} from "react";
import {CSS} from "@dnd-kit/utilities"
type PropsType={
id:string,
children:JSX.Element
}
function SortableItem({id,children}:PropsType){
const {attributes,listeners,setNodeRef,transition,transform} = useSortable({id});
const style = {
transition,
transform:CSS.Transform.toString(transform)
}
return (
<div ref={setNodeRef} {...attributes} {...listeners} style={style}>
{children}
</div>
)
}
export default SortableItem
使用:
var componentListWithId = componentList.map(item => ({...item, id: item.fe_id}));
function handleDragEnd(oldIndex: number, newIndex: number) {
console.log(oldIndex, newIndex)
dispatch(moveComponent({
oldIndex,newIndex
}))
}
return (
<SortableContainer onDragEnd={handleDragEnd} items={componentListWithId}>
{
componentList.map(c => {
const {fe_id, title, isHidden, isLocked,} = c
const titleDefaultClassName = styles.title
const selectedClassName = styles.selected
const titleClassName = classNames({
[titleDefaultClassName]: true,
[selectedClassName]: fe_id === selectedId
})
return (
<SortableItem key={fe_id} id={fe_id}>
<div className={styles.wrapper}>
<div className={titleClassName} onClick={() => handleTitleClick(fe_id)}>
{fe_id !== changingTitleId && title}
{fe_id === changingTitleId && <Input value={title}
onChange={(e) => changeTitle(e)}
onPressEnter={() => setChangingTitleId("")}
onBlur={() => setChangingTitleId("")}/>}
</div>
<div className={styles.handler}>
<Space>
<Button
onClick={() => changeHidden(fe_id, !isHidden)}
size="small"
className={!isHidden ? styles.btn : ""} icon={<EyeInvisibleOutlined/>}
type={isHidden ? "primary" : "default"} shape="circle"></Button>
<Button
onClick={() => changeLocked(fe_id)}
size="small"
className={!isLocked ? styles.btn : ""} icon={<LockOutlined/>}
type={isLocked ? "primary" : "default"} shape="circle"></Button>
</Space>
</div>
</div>
</SortableItem>
)
})
}
</SortableContainer>
)