드래그&드롭을 구현하기 위해서 react-DnD 라이브러리를 사용했다.
npm i react-dnd react-dnd-html5-backend
npm i immutability-helper
1️⃣ index.js
에서DndProvider
로 앱을 감싸준다.
// index.js
import React from 'react';
import ReactDom from 'react-dom';
import App from './App';
import { BrowserRouter as Router } from 'react-router-dom';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
ReactDom.render(
<DndProvider backend={HTML5Backend}> // (*)
<Router>
<App />
</Router>
</DndProvider>
document.querySelector('#root')
);
2️⃣ dnd.js
파일을 생성하고 사용할 itemType
을 정의한다. 파일명과 타입(예시: CARD
)이름은 본인 마음대로 사용하면 된다.
// dnd.js
export const ItemTypes = {
CARD: 'card',
};
3️⃣ List.js
에 moveCard
함수를 생성한다. 이 예제에서는 immutability-helper
를 사용했다.
List.js
// List.js
import React, { useState, useCallback } from 'react';
import styled from 'styled-components';
import update from 'immutability-helper';
import Item from 'components/Item/Item';
const Wrapper = styled.div`
border: 1px solid blue;
width: 80%;
margin: 0 auto;
background-color: ${props => (props.isOver ? 'pink' : 'white')};
`;
const List = () => {
const [cards, setCards] = useState([
{ id: 1, title: 'First' },
{ id: 2, title: 'Second' },
{ id: 3, title: 'Third' },
]);
const moveCard = useCallback( // (**) Reorder an array
(dragIndex, hoverIndex) => {
const dragCard = cards[dragIndex];
setCards(
update(cards, {
$splice: [
[dragIndex, 1], // Delete
[hoverIndex, 0, dragCard], // Add
],
})
);
},
[cards]
);
return (
<Wrapper>
{cards.map((item, index) => (
<Item
index={index}
id={item.id}
title={item.title}
moveCard={moveCard}
key={item.id}
/>
))}
</Wrapper>
);
};
export default List;
4️⃣ Item.js
에서 드래그&드롭 기능을 구현한다.
import React, { useRef } from 'react';
import styled from 'styled-components';
import { ItemTypes } from '../utils/item';
import { useDrag, useDrop } from 'react-dnd';
const Wrapper = styled.div`
border: 1px solid blue;
opacity: ${props => (props.isDragging ? 0 : 1)};
background-color: green;
`;
const Item = ({ id, title, index, moveCard }) => {
const ref = useRef(null); // (*)
const [, drop] = useDrop({ // (*)
accept: ItemTypes.CARD,
hover(item, monitor) {
if (!ref.current) {
return;
}
const dragIndex = item.index;
const hoverIndex = index;
if (dragIndex === hoverIndex) {
return;
}
const hoverBoundingRect = ref.current?.getBoundingClientRect();
const hoverMiddleY =
(hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
const clientOffset = monitor.getClientOffset();
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return;
}
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return;
}
moveCard(dragIndex, hoverIndex);
item.index = hoverIndex;
},
});
const [{ isDragging }, drag] = useDrag({ // (*)
item: { type: ItemTypes.CARD, id, index },
collect: monitor => ({
isDragging: monitor.isDragging(),
}),
});
drag(drop(ref)); // (*)
return (
<Wrapper ref={ref} isDragging={isDragging}>
<p>{title}</p>
</Wrapper>
);
};
export default Item;