React에서 드래그 앤 드롭을 구현하는 방법
39850 단어 typescriptjavascriptreacttutorial
소개
오늘 기사에서는 Trello 또는 ClickUp과 같은 애플리케이션과 유사한 인터페이스를 만들 것입니다. 아이디어는 앱의 기능을 확장할 수 있도록 기반을 만드는 것입니다.
기사의 끝에서 다음과 유사한 결과를 얻을 수 있습니다.
우리는 무엇을 사용할 것입니까?
오늘 우리는 다음과 같은 평범하지 않은 많은 도구를 사용하지 않을 것입니다.
Stitches - 경이로운 개발 경험이 있는 css-in-js 스타일링 라이브러리
radash - 문자열, 개체 및 배열을 처리하는 데 도움이 되는 일련의 함수를 제공하는 유틸리티 라이브러리
@dnd-kit/core - dnd를 구현하는 데 사용할 라이브러리입니다. 직관적이고 가벼우며 블록의 새로운 아이입니다
이들은 이 문서에서 사용된 라이브러리이지만 동일한 결과를 다른 라이브러리에서도 쉽게 복제할 수 있다는 점을 명심하십시오.
전제 조건
이 자습서를 따르려면 다음이 필요합니다.
TypeScript를 몰라도 아무 문제가 없을 것입니다. 항상 데이터 유형을 "무시"할 수 있지만 오늘의 예에서는 전체 프로세스가 훨씬 쉬워집니다.
시작하기
첫 번째 단계로 프로젝트 디렉토리를 생성하고 해당 디렉토리로 이동합니다.
yarn create vite react-dnd --template react-ts
cd react-dnd
이제 필요한 종속성을 설치할 수 있습니다.
yarn add radash @dnd-kit/core @stitches/react @fontsource/anek-telugu
상용구가 생성되면 애플리케이션 작업을 시작할 수 있지만 필요한 구성 요소를 만들기 전에 몇 가지 사항에 주의를 기울여야 합니다.
먼저 오늘 예제의 두 가지 중요한 구성 요소의 기초를 형성할 두 가지 프리미티브를 만들어 보겠습니다. 첫 번째 프리미티브는 Droppable.tsx
이며 기본적으로 드래그할 수 있는 여러 요소를 포함하는 영역입니다.
// @/src/primitives/Droppable.tsx
import { FC, ReactNode, useMemo } from "react";
import { useDroppable } from "@dnd-kit/core";
interface IDroppable {
id: string;
children: ReactNode;
}
export const Droppable: FC<IDroppable> = ({ id, children }) => {
const { isOver, setNodeRef } = useDroppable({ id });
const style = useMemo(
() => ({
opacity: isOver ? 0.5 : 1,
}),
[isOver]
);
return (
<div ref={setNodeRef} style={style}>
{children}
</div>
);
};
우리가 필요로 할 다른 프리미티브는 드래그할 수 있는 각 요소에 사용될 Draggable.tsx
입니다.
// @/src/primitives/Draggable.tsx
import { FC, ReactNode, useMemo } from "react";
import { useDraggable } from "@dnd-kit/core";
interface IDraggable {
id: string;
children: ReactNode;
}
export const Draggable: FC<IDraggable> = ({ id, children }) => {
const { attributes, listeners, setNodeRef, transform } = useDraggable({ id });
const style = useMemo(() => {
if (transform) {
return {
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
};
}
return undefined;
}, [transform]);
return (
<div ref={setNodeRef} style={style} {...listeners} {...attributes}>
{children}
</div>
);
};
우리가 생성한 두 프리미티브에 id
라는 공통 소품이 있음을 알아차렸을 것입니다. 분명히 그들은 식별자, 다른 사용 사례를 참조합니다.
yarn create vite react-dnd --template react-ts
cd react-dnd
yarn add radash @dnd-kit/core @stitches/react @fontsource/anek-telugu
// @/src/primitives/Droppable.tsx
import { FC, ReactNode, useMemo } from "react";
import { useDroppable } from "@dnd-kit/core";
interface IDroppable {
id: string;
children: ReactNode;
}
export const Droppable: FC<IDroppable> = ({ id, children }) => {
const { isOver, setNodeRef } = useDroppable({ id });
const style = useMemo(
() => ({
opacity: isOver ? 0.5 : 1,
}),
[isOver]
);
return (
<div ref={setNodeRef} style={style}>
{children}
</div>
);
};
// @/src/primitives/Draggable.tsx
import { FC, ReactNode, useMemo } from "react";
import { useDraggable } from "@dnd-kit/core";
interface IDraggable {
id: string;
children: ReactNode;
}
export const Draggable: FC<IDraggable> = ({ id, children }) => {
const { attributes, listeners, setNodeRef, transform } = useDraggable({ id });
const style = useMemo(() => {
if (transform) {
return {
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
};
}
return undefined;
}, [transform]);
return (
<div ref={setNodeRef} style={style} {...listeners} {...attributes}>
{children}
</div>
);
};
Droppable.tsx
의 경우 ID는 작업의 단계(백로그, 진행 중 등)에 해당합니다. Draggable.tsx
에서는 요소 id에 해당합니다(정수 또는 uuid일 수 있음). 이제 프리미티브가 있으므로 이를 사용할 구성 요소에서 작업할 수 있습니다.
DraggableElement.tsx
라는 가장 간단한 구성 요소부터 시작하여 작업 콘텐츠 렌더링을 담당합니다.// @/src/components/DraggableElement.tsx
import { FC, useMemo } from "react";
import { styled } from "@stitches/react";
import { Draggable } from "../primitives";
interface IDraggableElement {
identifier: string;
content: string;
}
export const DraggableElement: FC<IDraggableElement> = ({
identifier,
content,
}) => {
const itemIdentifier = useMemo(() => identifier, [identifier]);
return (
<Draggable id={itemIdentifier}>
<ElementWrapper>
<ElementText>{content}</ElementText>
</ElementWrapper>
</Draggable>
);
};
const ElementWrapper = styled("div", {
background: "#f6f6f6",
borderRadius: 10,
height: 120,
width: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center",
marginTop: 12,
});
const ElementText = styled("h3", {
fontSize: 18,
fontWeight: 600,
});
우리가 만들 마지막 구성 요소는
Column.tsx
이며 특정 작업과 관련된 각 요소를 렌더링하는 역할을 합니다.이 구성요소에 대해 말하자면
<DropPlaceholder />
라는 매우 중요한 요소가 있는데 바로 센서, 즉 잡고 있는 요소가 특정 컬럼/스테이지로 가는지를 감지하는 요소입니다.// @/src/components/Column.tsx
import { FC, useMemo } from "react";
import { styled } from "@stitches/react";
import * as _ from "radash";
import { Droppable } from "../primitives";
import { DraggableElement } from "./DraggableElement";
export interface IElement {
id: string;
content: string;
column: string;
}
interface IColumn {
heading: string;
elements: IElement[];
}
export const Column: FC<IColumn> = ({ heading, elements }) => {
const columnIdentifier = useMemo(() => _.camal(heading), [heading]);
const amounts = useMemo(
() => elements.filter((elm) => elm.column === columnIdentifier).length,
[elements, columnIdentifier]
);
return (
<ColumnWrapper>
<ColumnHeaderWrapper variant={columnIdentifier as any}>
<Heading>{heading}</Heading>
<ColumnTasksAmout>{amounts}</ColumnTasksAmout>
</ColumnHeaderWrapper>
<Droppable id={columnIdentifier}>
{elements.map((elm, elmIndex) => (
<DraggableElement
key={`draggable-element-${elmIndex}-${columnIdentifier}`}
identifier={elm.id}
content={elm.content}
/>
))}
<DropPlaceholder />
</Droppable>
</ColumnWrapper>
);
};
const Heading = styled("h3", {
color: "#FFF",
});
const ColumnWrapper = styled("div", {
width: 320,
padding: 10,
border: "dashed",
borderWidth: 2,
borderRadius: 10,
});
const DropPlaceholder = styled("div", {
height: 35,
backgroundColor: "transparent",
marginTop: 15,
});
const ColumnHeaderWrapper = styled("div", {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
variants: {
variant: {
backlog: {
background: "#F94892",
},
inProgress: {
background: "#5800FF",
},
inReview: {
background: "#ffb300",
},
done: {
background: "#24A19C",
},
},
},
padding: "0px 10px 0px 10px",
borderRadius: 10,
});
const ColumnTasksAmout = styled("span", {
display: "flex",
justifyContent: "center",
alignItems: "center",
width: 30,
height: 30,
borderRadius: 6,
color: "#FFF",
background: "rgba( 255, 255, 255, 0.25 )",
boxShadow: "0 8px 32px 0 rgba( 255, 255, 255, 0.18 )",
backdropFilter: "blur(5px)",
border: "1px solid rgba( 255, 255, 255, 0.18 )",
});
마지막으로
App.tsx
에서 필요한 종속성을 가져오고 앱에 포함할 단계와 관련된 일부 변수 및 상태를 정의합니다.그런 다음 드래그한 요소와 관련된 데이터를 인수로 수신하는
handleOnDragEnd()
라는 함수를 만들어야 합니다. 상태를 업데이트하기 위해(예: inProgress 단계를 inReview로 변경).마지막으로 각 단계에 해당하는 각 열을 매핑할 수 있으며 이러한 각 열의 소품에서 이와 관련된 요소를 전달할 수 있습니다.
import "@fontsource/anek-telugu";
import { useCallback, useState } from "react";
import { DndContext, DragEndEvent } from "@dnd-kit/core";
import { styled } from "@stitches/react";
import * as _ from "radash";
import { Column, IElement } from "./components";
const COLUMNS = ["Backlog", "In Progress", "In Review", "Done"];
export const DEFAULT_COLUMN = "backlog";
const DEFAULT_DATA_STATE: IElement[] = [
{
id: _.uid(6),
content: "Hello world 1",
column: DEFAULT_COLUMN,
},
{
id: _.uid(6),
content: "Hello world 2",
column: DEFAULT_COLUMN,
},
];
export const App = () => {
const [data, setData] = useState<IElement[]>(DEFAULT_DATA_STATE);
const handleOnDragEnd = useCallback(
({ active, over }: DragEndEvent) => {
const elementId = active.id;
const deepCopy = [...data];
const updatedState = deepCopy.map((elm): IElement => {
if (elm.id === elementId) {
const column = over?.id ? String(over.id) : elm.column;
return { ...elm, column };
}
return elm;
});
setData(updatedState);
},
[data, setData]
);
return (
<DndContext onDragEnd={handleOnDragEnd}>
<MainWrapper>
{COLUMNS.map((column, columnIndex) => (
<Column
key={`column-${columnIndex}`}
heading={column}
elements={_.select(
data,
(elm) => elm,
(f) => f.column === _.camal(column)
)}
/>
))}
</MainWrapper>
</DndContext>
);
};
const MainWrapper = styled("div", {
display: "flex",
justifyContent: "space-evenly",
backgroundColor: "#fff",
paddingTop: 40,
paddingBottom: 40,
fontFamily: "Anek Telugu",
height: "90vh",
});
결론
늘 그렇듯이 기사가 마음에 드셨기를 바라며 기존 프로젝트에 도움이 되었거나 단순히 사용해 보고 싶으셨기를 바랍니다.
기사에서 잘못된 부분을 발견했다면 댓글로 알려주시면 수정하겠습니다. 마치기 전에 이 기사의 소스 코드에 액세스하려면 github 저장소에 대한 링크here를 남겨둡니다.
Reference
이 문제에 관하여(React에서 드래그 앤 드롭을 구현하는 방법), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/franciscomendes10866/how-to-implement-drag-and-drop-in-react-3j8j
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
Reference
이 문제에 관하여(React에서 드래그 앤 드롭을 구현하는 방법), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/franciscomendes10866/how-to-implement-drag-and-drop-in-react-3j8j텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)