Skip to main content

拖拽排序

第三方库 

1) React dnd

样例有点复杂 不维护


2) React-beautiful-dnd

不维护了,不支持React18严格模式


3) Sortable.js

不依赖于React和Vue

不建议用于生产环境


4) React-sortable-hoc

不再维护


5) dnd kit

安装: npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities --save


基本构造:

根据官网示例,

有两个关键的文件: SortableContainer.tsx, SortableItem

1) SortableContainer.tsx

import React, { FC, useState } from "react";
import {
DndContext,
closestCenter,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
DragEndEvent,
} from '@dnd-kit/core';
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
verticalListSortingStrategy,
} from '@dnd-kit/sortable';

import DndItem from "./DndItem";

type ComponentType = {
fe_id: string;
title: string;
}

const Dnd: FC = () => {
const [items, setItems] = useState<ComponentType[]>([{ fe_id: "i1", title: "t1" }, { fe_id: "i2", title: "t2" }, { fe_id: "i3", title: "t3" },]);
const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
}))

// when dragging, the start position and end position
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event;
if (over == null) return;

if (active.id !== over.id) {
setItems((items) => {
const oldIndex = items.findIndex(component => component.fe_id === active.id);
const newIndex = items.findIndex(component => component.fe_id === over.id);

return arrayMove(items, oldIndex, newIndex);
});
}
}

const itemsWithId = items.map(component => {
return {
...component,
id: component.fe_id
}
});

return <DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<SortableContext
items={itemsWithId}
strategy={verticalListSortingStrategy}
>
{items.map(component => <DndItem key={component.fe_id} id={component.fe_id} title={component.title}/>)}
</SortableContext>
</DndContext>;
}

export default Dnd;


2) SortableItem.tsx

import React, { FC } from "react";
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';

type PropsType = {
id: string;
title: string;
}

const DndItem: FC<PropsType> = (props: PropsType) => {
const { id, title } = props;

const {
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id });

const style = {
transform: CSS.Transform.toString(transform),
transition,
border: "1px, solid #ccc",
margin: "10px 0",
background: "#f1f1f1",
};

return <div ref={setNodeRef} style={style} {...attributes} {...listeners}>
Item {id} {title}
</div>;
}

export default DndItem;


使用:

在我的项目中刚开始列表是这样的:


代码如下

{componentList.map((component) => {
const { fe_id, title, isHidden, isLocked } = component;
const itemClassName = classNames({
[styles.title]: true,
[styles.selected]: selectedId === fe_id,
});
return (
<div key={fe_id} className={styles.wrapper}>
<div
className={itemClassName}
onClick={() => {
handleTitleClicked(fe_id);
}}
>
{changingTitleId === fe_id && (
<Input
value={title}
onPressEnter={() => setChangingTitleId("")}
onBlur={() => setChangingTitleId("")}
onChange={handleInputChange}
/>
)}
{changingTitleId !== fe_id && title}
</div>
<div className={styles.handler}>
<Space>
<Button
size="small"
shape="circle"
icon={<EyeInvisibleOutlined />}
type={isHidden ? "primary" : "text"}
className={isHidden ? "" : styles.btn}
onClick={() => {
handleChangeHidden(fe_id, !isHidden);
}}
></Button>
<Button
size="small"
shape="circle"
icon={<LockOutlined />}
type={isLocked ? "primary" : "text"}
className={isLocked ? "" : styles.btn}
onClick={() => {
handleChangeLock(fe_id);
}}
></Button>
</Space>
</div>
</div>
);
})}


发现我的列表有个循环,刚好可以套入到SortableContainer return函数里面的那个{xx.map()}

并且我的项目中有两个地方会用到拖拽排序,一个地方是最左侧的图层,另一个地方是中间的画布,都需要拖拽排序的功能。所以考虑将所有元素都传入,使其可以复用在两个地方:

修改后的SortableContainer.tsx为:

import React, { FC, useState } from "react";
import {
DndContext,
closestCenter,
MouseSensor,
useSensor,
useSensors,
DragEndEvent,
} from "@dnd-kit/core";
import {
arrayMove,
SortableContext,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";

type PropsType = {
children: JSX.Element | JSX.Element[]; //被SortableContainer包裹起来的元素
items: Array<{ id: string; [key: string]: any }>;
onDragEnd: (oldIndex: number, newIndex: number) => void;
};

const SortableContainer: FC<PropsType> = (props: PropsType) => {
const { children, items, onDragEnd } = props;

const sensors = useSensors(
useSensor(MouseSensor, {
activationConstraint: {
distance: 8, // minimum trigger distance
},
})
);

// when dragging, the start position and end position
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event;
if (over == null) return;

if (active.id !== over.id) {
const oldIndex = items.findIndex(
(component) => component.fe_id === active.id
);
const newIndex = items.findIndex(
(component) => component.fe_id === over.id
);

onDragEnd(oldIndex, newIndex);

return arrayMove(items, oldIndex, newIndex);
}
}

return (
// Outside container
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
{children}
</SortableContext>
</DndContext>
);
};

export default SortableContainer;

可以发现,我的列表的内容就可以直接替代 {children}中的内容

然后修改后的SortableItem.tsx为:

import React, { FC } from "react";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";

type PropsType = {
id: string;
children: JSX.Element;
};

const SortableItem: FC<PropsType> = ({ id, children }) => {
const { attributes, listeners, setNodeRef, transform, transition } =
useSortable({ id });
const style = {
transform: CSS.Transform.toString(transform),
transition,
};
return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
{children}
</div>
);
};

export default SortableItem;


直接将SortableContainer和SortableItem应用到我的列表中

<SortableContainer items={componentListWithId} onDragEnd={handleDragEnd}>
{componentList.map((component) => {
const { fe_id, title, isHidden, isLocked } = component;
const itemClassName = classNames({
[styles.title]: true,
[styles.selected]: selectedId === fe_id,
});
return (
<SortableItem key={fe_id} id={fe_id}>
<div key={fe_id} className={styles.wrapper}>
<div
className={itemClassName}
onClick={() => {
handleTitleClicked(fe_id);
}}
>
{changingTitleId === fe_id && (
<Input
value={title}
onPressEnter={() => setChangingTitleId("")}
onBlur={() => setChangingTitleId("")}
onChange={handleInputChange}
/>
)}
{changingTitleId !== fe_id && title}
</div>
<div className={styles.handler}>
<Space>
<Button
size="small"
shape="circle"
icon={<EyeInvisibleOutlined />}
type={isHidden ? "primary" : "text"}
className={isHidden ? "" : styles.btn}
onClick={() => {
handleChangeHidden(fe_id, !isHidden);
}}
></Button>
<Button
size="small"
shape="circle"
icon={<LockOutlined />}
type={isLocked ? "primary" : "text"}
className={isLocked ? "" : styles.btn}
onClick={() => {
handleChangeLock(fe_id);
}}
></Button>
</Space>
</div>
</div>
</SortableItem>
);
})}
</SortableContainer>


也可以直接套用到我的图层中:

<SortableContainer items={componentListWithId} onDragEnd={handleDragEnd}>
<div className={styles.canvas}>
{componentList
.filter((c) => !c.isHidden)
.map((component) => {
const { fe_id, isLocked } = component;

const wrapperDefaultClassName = styles["component-wrapper"];
const selectedClassName = styles.selected;
const lockedClassName = styles.locked;
const wrapperClassName = classNames({
[wrapperDefaultClassName]: true,
[selectedClassName]: selectedId === fe_id, // selectedId == fe_id?
[lockedClassName]: isLocked,
});

return (
<SortableItem key={fe_id} id={fe_id}>
<div
className={wrapperClassName}
onClick={(e) => handelClick(e, fe_id)}
>
<div className={styles.component}>
{genComponent(component)}
</div>
</div>
</SortableItem>
);
})}
</div>
</SortableContainer>




Comments

Popular posts from this blog

Templates

  Template - Polymorphism is the ability of the same code to operate on different types. This ability to operate on multiple types reduces code duplication by allowing the same piece of code to be reused across the different types it can operate on. - Polymorphism comes in a variety of forms. What we are interested in at the moment is parametric polymorphism, meaning that we can write our code so that it is parameterized over what type it operates on.  -That is, we want to declare a type parameter T and replace int with T in the above code. -Then, when we want to call the function, we can specify the type for T and get the function we desire. C++ provides parametric polymorphism through templates. Templated Functions - We can write a templated function by using the keyword template followed by the template parameters in angle brackets (<>). - Unlike function parameters, template parameters may be types, which are specified with typename where the type of the parameter wo...

useMemo的使用场景

 useMemo是用来缓存 计算属性 的。 计算属性是函数的返回值,或者说那些以返回一个值为目标的函数。 有些函数会需要我们手动去点击,有些函数是直接在渲染的时候就执行,在DOM区域被当作属性值一样去使用。后者被称为计算属性。 e.g. const Component = () => { const [ params1 , setParams1 ] = useState ( 0 ); const [ params2 , setParams2 ] = useState ( 0 ); //这种是需要我们手动去调用的函数 const handleFun1 = () => { console . log ( "我需要手动调用,你不点击我不执行" ); setParams1 (( val ) => val + 1 ); }; //这种被称为计算属性,不需要手动调用,在渲染阶段就会执行的。 const computedFun2 = () => { console . log ( "我又执行计算了" ); return params2 ; }; return ( < div onClick = { handleFun1 } > //每次重新渲染的时候我就会执行 computed: { computedFun2 () } </ div > ); }; 上面的代码中,在每次点击div的时候,因为setParams1的缘故,导致params1改变,整个组件都需要重新渲染,也导致comptedFunc2()也需要重新计算。如果computedFunc2的计算量很大,这时候重新计算会比较浪费。 可以使用useMemo: const Com = () => { const [ params1 , setParams1 ] = useState ( 0 ); const [ params2 , setParams2 ] = useState ( 0 ); //这种是需要我们手动去调用的函数 const handleFun1 ...

Valgrind

  Using Valgrind to check memory How to use Valgrind -To valgrind the program, run the valgrind command and give it our program name as an argument. -For example, if we want to run ./myProgram hello 42, we can simply run Valgrind ./myProgram hello 42.  Uninitialized Values -When we run the program, we may use uninitialized values. It needs to be fixed. Valgrind can tell us about the use of uninitialized values. But it only tell when the control flow of the program depends on the unitialized value. For example, uninitialized value appears in the conditional expression of an if, or a loop, or in the switch statement. -If we want to know where the uninitialized value is from, we can use Valgrind    --track-origins=yes ./myProgram -Using -fsanitize=address can find a lot of problems that Memcheck cannot.  -We can use Valgrind with GDB to debug. We can run: --vgdb=full --vgdb-error=0., then Valgrind will stop on the first error that it encounters and give control to ...