// 드래그 앤 드롭, drag & drop
- 마우스나 터치스크린을 이용해서 객체를 이동시킬 수 있게 하는 기술
// react-beautiful-dnd
- drag & drop 을 구현하기 위한 라이브러리
- DragDropContext, Droppable, Draggable의 세 가지 주요 컴포넌트로 구성되어 있다.
- DragDropContext : 전반적인 프레임워크
- Droppable : 드롭 가능한 영역
- Draggable : 드래그 가능한 각 요소
1. 설치
npm install react-beautiful-dnd
타입스크립트를 사용한다면 @types도 추가로 설치한다.
npm install --save-dev @types/react-beautiful-dnd
2. DragDropContext 사용
<DragDropContext onDragEnd={handleDragEnd}>
<!-- children 영역 -->
</DragDropContext>
- onDragEnd : 드래그가 끝난 후 호출되는 함수
- 매개변수로 드래그된 객체에 대한 정보를 받을 수 있다. - children : jsx 코드
3. Droppable 사용
<Droppable droppableId="">{(provided) => jsx코드}</Droppable>
- children : jsx 코드를 바로 사용할 수 없고, 함수를 호출하여 jsx 코드를 return 한다.
- provided : 자체적으로 제공되는 DroppableProvided.
- provided.placeholder를 사용하여 리스트 사이즈를 유지할 수 있다.
4. Draggable 사용
<Draggable key={} draggableId={} index={}>
{(provided) => jsx코드}
</Draggable>
- key와 draggableId의 값은 같아야 한다.
- children : droppable의 children과 마찬가지로 jsx 코드를 바로 사용할 수 없고, 함수를 호출하여 jsx 코드를 return 한다.
- jsx 코드에 props로
ref={provided.innerRef}
{...provided.dragHandleProps}
{...provided.draggableProps}
를 사용한다.
import DraggableCard from './DraggableCard';
import { Droppable } from 'react-beautiful-dnd';
import styled from 'styled-components';
import { useForm } from 'react-hook-form';
import { IToDo, toDoState } from '../store/atoms';
import { RecoilLoadable, useSetRecoilState } from 'recoil';
const Wrapper = styled.div`
width: 300px;
padding-top: 10px;
background-color: ${(props) => props.theme.boardColor};
border-radius: 5px;
min-height: 300px;
display: flex;
flex-direction: column;
`;
const Title = styled.h2`
text-align: center;
font-weight: 600;
margin-bottom: 10px;
margin-top: 10px;
font-size: 18px;
color: #a90404;
`;
interface IAreaProps {
isDraggingOver: boolean;
isDraggingFromThisWith: boolean;
}
const Area = styled.div<IAreaProps>`
background-color: ${(props) =>
props.isDraggingOver
? '#ffb8b1'
: props.isDraggingFromThisWith
? '#ffe0e0'
: 'transparent'};
flex-grow: 1;
transition: background-color 0.3s ease-in-out;
padding: 20px;
`;
const Form = styled.form`
width: 100%;
input {
width: 100%;
}
`;
interface IBoardProps {
toDos: IToDo[];
boardId: string;
}
interface IForm {
toDo: string;
}
function Board({ toDos, boardId }: IBoardProps) {
const setToDos = useSetRecoilState(toDoState);
const { register, setValue, handleSubmit } = useForm<IForm>();
const onValid = ({ toDo }: IForm) => {
const newToDo = {
id: Date.now(),
text: toDo,
};
setToDos((allBoards) => {
return { ...allBoards, [boardId]: [...allBoards[boardId], newToDo] };
});
setValue('toDo', '');
};
return (
<Wrapper>
<Title>{boardId}</Title>
<Form onSubmit={handleSubmit(onValid)}>
<input
{...register('toDo', { required: true })}
type="text"
placeholder={`Add task on ${boardId}`}
/>
</Form>
<Droppable droppableId={boardId}>
{(provided, info) => (
<Area
isDraggingOver={info.isDraggingOver}
isDraggingFromThisWith={Boolean(info.draggingFromThisWith)}
ref={provided.innerRef}
{...provided.droppableProps}
>
{toDos.map((toDo, index) => (
<DraggableCard
key={toDo.id}
toDoId={toDo.id}
toDoText={toDo.text}
index={index}
/>
))}
{provided.placeholder}
</Area>
)}
</Droppable>
</Wrapper>
);
}
export default Board;
import { Draggable } from 'react-beautiful-dnd';
import styled from 'styled-components';
import React from 'react';
const Card = styled.div<{ isDragging: boolean }>`
border-radius: 5px;
margin-bottom: 5px;
padding: 10px 10px;
background-color: ${(props) => props.theme.cardColor};
box-shadow: ${(props) =>
props.isDragging ? '0px 2px 5px rgb(116 71 71 / 50%)' : 'none'};
`;
interface IDraggableCardProps {
toDoId: number;
toDoText: string;
index: number;
}
function DraggableCard({ toDoId, toDoText, index }: IDraggableCardProps) {
return (
<Draggable draggableId={`${toDoId}`} index={index}>
{(provided, snapshot) => (
<Card
isDragging={snapshot.isDragging}
ref={provided.innerRef}
{...provided.dragHandleProps}
{...provided.draggableProps}
>
{toDoText}
</Card>
)}
</Draggable>
);
}
export default React.memo(DraggableCard);
// React.memo
드래그를 시작하면 위치가 변하지 않은 컴포넌트들까지 모두 재렌더링이 된다.
이를 막기 위해서 React.memo로 Draggable 컴포넌트를 감싸준다.
React.memo(Draggable 컴포넌트명)
import { Draggable } from 'react-beautiful-dnd';
import styled from 'styled-components';
import React from 'react';
const Card = styled.div`
border-radius: 5px;
margin-bottom: 5px;
padding: 10px 10px;
background-color: ${(props) => props.theme.cardColor};
`;
interface IDraggableCardProps {
toDo: string;
index: number;
}
function DraggableCard({ toDo, index }: IDraggableCardProps) {
return (
<Draggable key={toDo} draggableId={toDo} index={index}>
{(provided) => (
<Card
ref={provided.innerRef}
{...provided.dragHandleProps}
{...provided.draggableProps}
>
{toDo}
</Card>
)}
</Draggable>
);
}
export default React.memo(DraggableCard);
※ React 18 에서는 <React.StrictMode>를 제거해야 된다.
※ 현재 react-beautiful-dnd는 업데이트가 중단되었다.
차차 다른 라이브러리를 찾아서 글을 수정할 예정..
'클라이언트 > React' 카테고리의 다른 글
[리액트(React)] Gatsby (0) | 2024.04.26 |
---|---|
[리액트(React)] Vercel로 React 프로젝트 배포하기 (0) | 2024.04.19 |
[리액트(React)] Github page 배포 (0) | 2024.04.01 |
[리액트(React)] react-hook-form (0) | 2024.03.29 |
[리액트(React)] 리코일(Recoil) (0) | 2024.03.27 |