방망이 깎는 코모
리액트로 드래그 앤 드롭 구현하는 방법 본문
Dragula는 웹 애플리케이션에서 드래그 앤 드랍 기능을 쉽게 구현할 수 있도록 도와주는 자바스크립트 라이브러리다. Drag and drop so simple it hurts 라는 슬로건처럼, Dragula API를 이용하여 DOM 요소들 간의 드래그 앤 드랍 기능을 매우 간단하고 손쉽게 만들 수 있다.
Dragula 설치
npm을 통해 애플리케이션에 Dragula를 설치한다.
npm install react-dragula --save
(옵션) Dragula CSS
Dragula가 정상적으로 작동하기 위해 지켜져야 할 CSS 스타일들이 있다. 총 4개의 CSS 클래스를 사용하며 해당 규칙은 아래와 같다. 자세한 CSS 규칙은 dist/dragula.css
에서 확인 가능하다.
CSS 클래스 | 설명 |
gu-unselectable |
드래깅 할 때 mirrorContainer 요소에 추가; 드래깅 하는 동안 mirrorContainer를 스타일링 하는데 사용 |
gu-transit |
드래깅 될 때 원본 요소에 추가; 투명도(opacity)만 조절 |
gu-mirror |
미러 이미지에 추가; (미러 이미지는 초기 컨테이너가 아닌 mirrorContainer에 추가됨) |
gu-hide |
display:none 을 적용하기 위한 helper 클래스 |
Dragula 기본 사용 방법
JavaScript는 직접 DOM요소를 선택하고, Dragula()
함수를 호출하여 생성된 drake
객체에게 전달된다. 객체가 제공하는 이벤트와 메서드등을 사용하여 드래그 앤 드랍 기능을 구현할 수 있다.
반면, 리액트에서는 직접 DOM을 선택하지 않고 useRef 훅을 사용하여 DOM요소에 접근한다. 그리고 컴포넌트가 마운트 될 때, useEffect 훅 내에서 Dragula를 초기화 하고, ref
를 Dragula()
함수에 전달하여 드래그 앤 드롭 기능을 활성화시킨다.
Dragula(ref, options);
두 번째 매개변수인 options
는 드래그 앤 드롭 동작을 미세하게 조정하는데 사용되는 설정이다. 그 중 revertOnSpill
은 드래그 중인 요소를 드랍할 수 없는 위치에 놓았을 때, 시작 위치로 돌아갈지(true), 현재 위치에 남을지(false) 결정한다. removeOnSpill
는 드래그 중인 요소를 유효하지 않은 위치에 놓았을 때, 제거될 지 여부를 결정한다. true일 경우 DOM에서 제거되고, false일 경우 revertOnSpill
에서 설정한 방식으로 돌아간다.
(참고)
- 컨테이너 밖에 드래그하면 요소 제거 :
revertOnSpill: false + removeOnSpill: true
- 컨테이너 밖에 드래그해도 요소 원래 위치로 복귀 :
revertOnSpill: true + removeOnSpill: false
(이론) 리액트에서 Dragula를 사용하는 방법
클래스형 컴포넌트 방식
아래는 (공식문서에서 제공하는) 클래스 컴포넌트 방식으로 Dragula 기능을 구현하는 방식이다.
import * as React from "react";
import * as ReactDOM from 'react-dom';
import Dragula from 'react-dragula';
export class App extends React.Component {
render () {
return <div className='container' ref={this.dragulaDecorator}>
<div>Swap me around</div>
<div>Swap her around</div>
<div>Swap him around</div>
<div>Swap them around</div>
<div>Swap us around</div>
<div>Swap things around</div>
<div>Swap everything around</div>
</div>;
},
dragulaDecorator = (componentBackingInstance) => {
if (componentBackingInstance) {
let options = { };
Dragula([componentBackingInstance], options);
}
};
});
ReactDOM.render(<App />, document.getElementById('examples'));
함수형 컴포넌트 방식
Dragula 공식문서에서는 클래스형 컴포넌트 방식의 사용 방법만 안내했지만, 개인적으로 함수형 컴포넌트 방식을 선호하기 때문에 코드를 수정해보았다. 클래스형 컴포넌트의 dragulaDecorator
함수는 useRef와 useEffect 훅을 사용하여 함수형 컴포넌트에서도 구현할 수 있다.
useRef 훅을 사용하여 참조(ref
)를 생성하는데, 생성된 참조의 초기값으로 null
을 전달한다. 왜냐하면 컴포넌트가 마운트되기 전에는 참조하려는 DOM요소가 존재하지 않기 때문이다. 컴포넌트가 마운트된 후, 참조(ref
)는 ref 속성을 통해서 실제 DOM요소를 .current
프로퍼티에 할당한다.
import React, { useRef, useEffect } from 'react';
import ReactDOM from 'react-dom';
import Dragula from 'react-dragula';
function App() {
const containerRef = useRef(null);
useEffect(() => {
if (containerRef.current) {
Dragula([containerRef.current]);
}
return () => {
// 컴포넌트가 unmount시 실행될 cleanup함수 추가
};
}, []);
return (
<div className='container' ref={containerRef}>
<div>Swap me around</div>
<div>Swap her around</div>
<div>Swap him around</div>
<div>Swap them around</div>
<div>Swap us around</div>
<div>Swap things around</div>
<div>Swap everything around</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('examples'));
(실습) 리액트에서 Dragula를 사용하는 방법
useState를 이용한 상태 관리
할 일 목록(todoItems
), 입력 값(inputValue
), 카테고리 값(categoryValue
), 카테고리 이름(categoryName
)을 useState 훅을 이용하여 상태를 추적하고 업데이트하였다.
const [todoItems, setTodoItems] = useState([]);
const [inputValue, setInputValue] = useState("");
const [categoryValue, setCategoryValue] = useState("0");
const [categoryName, setCategoryName] = useState("");
useRef를 이용한 DOM요소 접근
Dragula 컨테이너로 사용될 3개의 <ul> DOM요소를 useRef를 통해 각각 참조한다. 다만 컴포넌트가 마운트 되기 전에는 해당 DOM요소가 존재하지 않으므로, null
을 초기값으로 설정하였다.
const todoOptionsRef = useRef(null);
const todoTodayRef = useRef(null);
const todoRemovedRef = useRef(null);
useEffect를 이용한 동작 설정
컴포넌트가 마운트된 후 Dragula를 설정하고 초기화 한다. 이 과정에서 useRef로 참조된 <ul> 요소들이 'Dragula 컨테이너'로써 등록된다. 의존성 배열을 빈 배열로 전달함으로써 Dragula는 처음 컴포넌트가 마운트 될 때만 실행하도록 설정하였다. 그리고 간단한 기능 구현이기 때문에 유효성 검사를 위한 if문은 생략하였다.
useEffect(() => {
const containers = [
todoOptionsRef.current,
todoTodayRef.current,
todoRemovedRef.current,
];
let options = {};
Dragula(containers, options);
}, []);
완성 화면
마치며
Dragula 라이브러리를 이용하여 JavaScript로 '드래그 앤 드롭' 기능을 구현하는 방법을 학습했다. Dragula의 So simple it hurts 라는 슬로건처럼, 그 과정이 너무 간단해서 허탈하기까지 했다. 그래서 이제 막 배우기 시작한 리액트도 실습할 겸 해당 기능을 리액트로 재 구현해보았다.
간단한 기능이었지만, 리액트 개념 중 useState와 useEffect만 배우고 시작한 상태라, 내게 추가적으로 필요한 훅이 useRef 라는 것을 알기까지 시간이 좀 걸렸다. 더이상 document.querySelector()
를 쓸 필요가 없다는 사실은 아직도 믿기지 않는다.
다음에는 '휴지통' 목록에 아이템이 없을 때는 '전체 삭제(아이콘)'이 제거되고, 아이템이 하나라도 추가되면 등장할 수 있도록 수정해보려고 한다. 사실 해당 기능을 구현해보려고 시도했으나, useRef를 통해 DOM 요소를 받아서 실시간으로 상태관리를 해야하는 부분에서 생각처럼 동작하고 있지 않다. 그리고 중간에 코드를 수정하느라 카테고리 값(categoryValue
) 로직도 비효율적으로 설계되어 이 부분도 함께 수정할 필요가 있다. 아직 리액트 훅에 대해 기초적인 개념 밖에 없다보니 추가적인 학습 후에 수정할 생각이다.
참고 문헌
1. Dragula, Official React wrapper for dragula : https://github.com/bevacqua/react-dragula?tab=readme-ov-file
2. Dragula, Official Document : https://github.com/bevacqua/dragula?tab=readme-ov-file
'개념정리' 카테고리의 다른 글
무한스크롤 (Infinite Scroll) (1) | 2024.01.24 |
---|---|
RESTful API 정의 및 사용 방법 (0) | 2024.01.10 |
HTTP와 HTTPS 차이점 및 HTTPS 적용 방법 (1) | 2024.01.09 |
브라우저 저장소의 종류와 특징 : localStorage, sessionStorage, IndexedDB (0) | 2024.01.05 |
자바스크립트 Class를 활용한 모듈화 기법 (0) | 2024.01.04 |