Reactjs - 드래그 앤 드롭 리스트 구현하기
반응형
React에서 드래그 앤 드롭 리스트 구현하기 (MUI + React-DnD)
레이어 목록을 드래그하여 순서를 변경할 수 있는 컴포넌트를 구현해보겠습니다. Material-UI와 React-DnD를 활용하여 직관적인 UI와 부드러운 드래그 앤 드롭 기능을 구현할 수 있습니다.
필요한 패키지 설치
# Material-UI 관련 패키지
npm install @mui/material @mui/icons-material @emotion/react @emotion/styled
# React-DnD 관련 패키지
npm install react-dnd react-dnd-html5-backend
# 불변성 관리를 위한 패키지
npm install immutability-helper
주요 기능
- 드래그 앤 드롭으로 항목 순서 변경
- 최상단/최하단 이동 버튼
- 한 칸 위/아래 이동 버튼
- 드래그 중인 항목의 시각적 피드백
컴포넌트 구조
LayerList
: 메인 컴포넌트Layer
: 개별 드래그 가능한 항목 컴포넌트
전체 코드 설명
import React, { useState, useCallback } from 'react';
import {
List,
ListItem,
ListItemIcon,
IconButton,
Box
} from '@mui/material';
import {
DragIndicator,
KeyboardDoubleArrowUp,
KeyboardDoubleArrowDown,
KeyboardArrowUp,
KeyboardArrowDown
} from '@mui/icons-material';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import update from 'immutability-helper';
// 드래그 가능한 아이템 컴포넌트
const Layer = ({ id, text, index, moveItem, moveToTop, moveToBottom, moveUp, moveDown }) => {
const ref = React.useRef(null);
// 드래그 로직 설정
const [{ isDragging }, drag] = useDrag({
type: 'item',
item: { id, index },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
// 드롭 로직 설정
const [, drop] = useDrop({
accept: 'item',
hover: (item, monitor) => {
if (!ref.current) return;
const dragIndex = item.index;
const hoverIndex = index;
if (dragIndex === hoverIndex) return;
moveItem(dragIndex, hoverIndex);
item.index = hoverIndex;
},
});
// ref 설정으로 드래그&드롭 기능 활성화
drag(drop(ref));
return (
<ListItem
ref={ref}
style={{
opacity: isDragging ? 0.5 : 1,
cursor: 'move',
backgroundColor: '#fff',
marginBottom: '4px',
border: '1px solid #eee',
borderRadius: '4px'
}}
>
{/* 왼쪽 드래그 핸들 */}
<ListItemIcon>
<DragIndicator />
</ListItemIcon>
{/* 아이템 내용 */}
<Box sx={{ flex: 1 }}>{text}</Box>
{/* 오른쪽 이동 버튼들 */}
<Box>
<IconButton size="small" onClick={() => moveToTop(index)}>
<KeyboardDoubleArrowUp />
</IconButton>
<IconButton size="small" onClick={() => moveUp(index)}>
<KeyboardArrowUp />
</IconButton>
<IconButton size="small" onClick={() => moveDown(index)}>
<KeyboardArrowDown />
</IconButton>
<IconButton size="small" onClick={() => moveToBottom(index)}>
<KeyboardDoubleArrowDown />
</IconButton>
</Box>
</ListItem>
);
};
// 메인 LayerList 컴포넌트
const LayerList = () => {
// 초기 아이템 데이터
const [items, setItems] = useState([
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' },
{ id: 4, text: 'Item 4' },
]);
// 드래그로 아이템 이동
const moveItem = useCallback((dragIndex, hoverIndex) => {
setItems((prevItems) =>
update(prevItems, {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, prevItems[dragIndex]],
],
}),
);
}, []);
// 최상단으로 이동
const moveToTop = useCallback((index) => {
setItems((prevItems) => {
const newItems = [...prevItems];
const [removed] = newItems.splice(index, 1);
newItems.unshift(removed);
return newItems;
});
}, []);
// 최하단으로 이동
const moveToBottom = useCallback((index) => {
setItems((prevItems) => {
const newItems = [...prevItems];
const [removed] = newItems.splice(index, 1);
newItems.push(removed);
return newItems;
});
}, []);
// 한칸 위로 이동
const moveUp = useCallback((index) => {
if (index === 0) return;
setItems((prevItems) => {
const newItems = [...prevItems];
[newItems[index - 1], newItems[index]] = [newItems[index], newItems[index - 1]];
return newItems;
});
}, []);
// 한칸 아래로 이동
const moveDown = useCallback((index) => {
setItems((prevItems) => {
if (index === prevItems.length - 1) return prevItems;
const newItems = [...prevItems];
[newItems[index], newItems[index + 1]] = [newItems[index + 1], newItems[index]];
return newItems;
});
}, []);
return (
<DndProvider backend={HTML5Backend}>
<List sx={{ width: '100%', maxWidth: 600, margin: '0 auto' }}>
{items.map((item, index) => (
<Layer
key={item.id}
id={item.id}
text={item.text}
index={index}
moveItem={moveItem}
moveToTop={moveToTop}
moveToBottom={moveToBottom}
moveUp={moveUp}
moveDown={moveDown}
/>
))}
</List>
</DndProvider>
);
};
export default LayerList;
주요 코드 설명
1. Layer 컴포넌트
드래그 가능한 개별 항목을 표현하는 컴포넌트입니다.
const Layer = ({ id, text, index, moveItem, moveToTop, moveToBottom, moveUp, moveDown }) => {
const ref = React.useRef(null);
// useDrag: 드래그 기능 설정
const [{ isDragging }, drag] = useDrag({
type: 'item',
item: { id, index },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
// useDrop: 드롭 기능 설정
const [, drop] = useDrop({
accept: 'item',
hover: (item, monitor) => {
if (!ref.current) return;
const dragIndex = item.index;
const hoverIndex = index;
if (dragIndex === hoverIndex) return;
moveItem(dragIndex, hoverIndex);
item.index = hoverIndex;
},
});
// drag와 drop 기능을 ref에 연결
drag(drop(ref));
// ... JSX 반환
};
2. 이동 관련 함수들
// 드래그로 아이템 이동
const moveItem = useCallback((dragIndex, hoverIndex) => {
setItems((prevItems) =>
update(prevItems, {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, prevItems[dragIndex]],
],
}),
);
}, []);
// 최상단으로 이동
const moveToTop = useCallback((index) => {
setItems((prevItems) => {
const newItems = [...prevItems];
const [removed] = newItems.splice(index, 1);
newItems.unshift(removed);
return newItems;
});
}, []);
// 한칸 위로 이동
const moveUp = useCallback((index) => {
if (index === 0) return;
setItems((prevItems) => {
const newItems = [...prevItems];
[newItems[index - 1], newItems[index]] = [newItems[index], newItems[index - 1]];
return newItems;
});
}, []);
주요 라이브러리 설명
React-DnD
useDrag
: 드래그 가능한 요소를 정의useDrop
: 드롭 가능한 영역을 정의DndProvider
: 드래그 앤 드롭 컨텍스트 제공
Material-UI (MUI)
List/ListItem
: 리스트 구조 제공IconButton
: 이동 버튼 구현Box
: 레이아웃 컨테이너
immutability-helper
- 불변성을 유지하면서 배열/객체 업데이트
update()
함수로 효율적인 상태 업데이트
스타일링
<ListItem
style={{
opacity: isDragging ? 0.5 : 1,
cursor: 'move',
backgroundColor: '#fff',
marginBottom: '4px',
border: '1px solid #eee',
borderRadius: '4px'
}}
>
사용 방법
import LayerList from './components/LayerList';
function App() {
return (
<div>
<LayerList />
</div>
);
}
이 컴포넌트는 드래그 앤 드롭으로 항목을 재정렬할 수 있으며, 버튼을 통해 항목의 위치를 직접 조정할 수 있습니다. Material-UI의 디자인 시스템을 활용하여 깔끔하고 일관된 UI를 제공합니다.
반응형
'개발' 카테고리의 다른 글
nodejs - 효율적인 에러 처리: try-catch 사용 전략 (0) | 2024.11.08 |
---|---|
Reactjs - useEffect 와 의존성 배열 완벽 가이드 (0) | 2024.11.06 |
Reactjs - 이미지 첨부 컴포넌트 (0) | 2024.11.06 |
Apache NiFi (0) | 2024.11.01 |