내부적으로 Redux를 사용하고 있어 컨셉 자체는 Redux 구조와 닮아있음.
drag-n-drop이 view에 대한 처리이긴 하지만 내부적으로는 데이터만을 가지고 처리를 함.
화면을 드래그할 때 컴포넌트 또는 DOM의 노드가 드래그되는 것이 아니라 특정 type의 item이 드래그되고 있다고 생각해야 함.
Item은 드래그되는 대상을 설명하는 js 객체임. 즉, 상기 내용에 따르면 체스 게임에서 말을 들 때 말이 드래그 되고 있는 것이 아니라 {fromCell:'C5', piece:'knight'}가 드래그 되고 있다고 생각해야 함. 이렇게 처리하면 컴포넌트들이 상호배타적으로 존재할 수 있게 유지가 가능.
Type은 앱 내에서 고유하게 식별되는 전체 항목 클래스라고 볼 수 있음 체스 게임으로 예를 들면 'piece'가 유형이 될 수 있음. Type을 통해 같은 type의 item들끼리만 호환이 가능하도록 할 수 있음.
어차피 우리는 item으로 조작하는 거니까 컴포넌트들은 서로를 몰라도 된다!
drag-n-drop은 항상 상태를 가지고 있어야 함. 드래그 작업 중 임/아님, 현재 아이템(타입)이 있음/없음을 체크 해야 함. react-dnd는 이 상태에 대한 정보를 monitor라는 객체를 통해 컴포넌트에 노출함. 모니터를 사용해서 컴포넌트의 props를 업데이트할 수 있음.
또한 drag-n-drop 상태를 추적해야하는 각각의 컴포넌트마다 collect 함수를 정의해서 monitor가 담고있는 정보들을 검색할 수 있음. 그런 다음 react-dnd가 collect 함수를 적시에 호출해 리턴 값을 컴포넌트의 props에 업데이트함.
예를 들어, 체스 말이 드래그되는 동안 체스 셀을 강조하려면 Cell 컴포넌트의 collect 함수는 아래와 같을 수 있음.
const collect = (monitor) => {
highlighted: monitor.canDrop(),
hovered: monitor.isOver()
}
이렇게 하면 모니터가 가지고 있는 정보를 Cell 컴포넌트의 props로 전달 가능함.
돔에 대한 드래그앤드롭 이벤트는 백엔드에서 처리하는데, 프론트엔드는 리액트를 통해 컴포넌트로 이벤트를 설명을 하고 있음. 그렇다면 어떤 DOM 노드가 드래그 앤 드롭 이벤트를 처리하는지 백엔드가 어떻게 알까?
react-dnd에서는 connector라는 기능을 제공해서 react 컴포넌트에서 dragSource, dragPreview, dropTarget 같은 사전에 정의된 롤을 DOM 노드에 할당할 수 있는데, 이를 통해 백엔드는 커넥터가 리턴하는 함수를 사용해 DOM 노드의 이벤트를 처리함.
collect 함수의 첫번째 인자가 바로 커넥터임.
const collect = (connect, monitor) => {
highlighted: monitor.canDrop(),
hovered: monitor.isOver(),
connectDropTarget: connect.dropTarget()
}
컴포넌트에서는 monitor에서 얻은 데이터와 connect에서 얻은 함수 모두에 접근이 가능함.
import classSet from 'classnames';
const Cell = (props) => {
const { highlighted, hovered, connectDropTarget } = props;
return connectDropTarget(
<div className={classSet({
'Cell': true,
'Cell--highlighted': highlighted,
'Cell--hovered': hovered
})}>
{props.children}
</div>
);
}
export default Cell;
connectDropTarget을 호출하여 react-dnd에게 컴포넌트의 루트 DOM 노드가 유효한 대상이고 해당 노드의 이벤트가 백엔드에서 처리해야함을 알림. 내부적으로는 react 요소에 콜백 ref를 연결해 작동함.
Item과 Type, Monitor와 Connector로 react-dnd가 컴포넌트에 어떠한(What) props를 전달하는지 알게 되었음!! 근데 실제로 React의 컴포넌트에는 어떻게(How) 전달해야할까? 그리고 드래그 앤 드롭 이벤트에 대한 응답으로 사이드 이펙트를 수행하려면 어떻게 해야할까?
Drag Sources와 Drop Targets이 item,type,side effect,collector 들을 컴포넌트와 결합시키는 역할을 함.
컴포넌트 또는 요소를 드래그 가능하게 하려면 먼저 얘네들을 Drag Source로 래핑해야 함. 각각의 Drag Source는 특정 Type에 대해 등록되어 있으며, 컴포넌트의 props에서 item을 생성하는 방법을 구현해야 함. 드래그 앤 드롭을 처리하는 메소드를 선택적으로 지정할 수도 있고, Drag Source에서 요소에 대한 collector 함수를 지정할 수도 있음.
Drop Target은 Drag Source와 매우 비슷한데, Drag Source와 다른 점은 단일 Drop Target이 한번에 여러 item type에 대해 등록될 수 있으며, item을 생성하는 대신 해당 item의 hover나 drop을 처리할 수 있다는 점.
예를 들어, Drop Target은 'card' item과 'list' item 둘 다 등록될 수 있다. 'card' 가 드롭되면 해당 카드를 카드 목록으로 이동시키고, 'list'가 드롭되면 해당 리스트를 이동시키는 작업 등을 할 수 있음. 하지만 Drop Source의 경우 1:1로 등록된 항목만 처리하고, 해당 카드를 드래그할 때 생성해야 함.
생각해보면 당연한 게 드래그는 드래그 앤 드롭의 출발점이고 드롭은 드래그 앤 드롭의 종착점이다. 드래그될 땐 특정 아이템에 해당되는 각각의 이벤트가 호출되어야 하는 게 맞고, 드롭할 땐 이미 드래그 됐을 때 어떤 아이템이 드래그 되었는지에 대한 정보를 얻었으니 이에 따라 분기 처리만 하면 되니까!
React-dnd는 HTML5의 드래그 앤 드롭 API를 사용함.
이로 인한 장점은 DragPreview를 통해 커서 이동 시에 따로 Draw 처리를 할 필요가 없다는 점, 파일 드롭 이벤트를 처리할 유일한 방법이라는 점.
하지만 터치 스크린에서 작동하지 않고 IE 한정 커스터마이징 기능이 적음.
그래서 추가 라이브러리를 통해 단점을 보완하는데 이를 백엔드라고 부름. 이를 통해 모바일 등의 다른 환경에서도 react-dnd를 적용할 수 있도록 만듦.
갑자기 백엔드가 튀어나와서 당황했지만 그냥 내가 몰라도 되는 뒷단이라고 생각하니까 간단했다.
react-dnd 구성에는 다음과 같은 요소들이 있다.