React, Next, Redux/⚛ React.JS

React에서 dnd 구현하기 (react-dnd)

DarrenKwonDev 2021. 7. 6. 21:22

web API로서의 dnd API

https://developer.mozilla.org/ko/docs/Web/API/HTML_Drag_and_Drop_API

 

HTML 드래그 앤 드롭 API - Web API | MDN

HTML 드래그 앤 드롭 인터페이스는 파이어폭스와 다른 브라우저에서 어플리케이션이 드래그 앤 드롭 기능을 사용하게 해줍니다.

developer.mozilla.org

 

onDragStart, onDragEnter, onDragEnd 이벤트 리스너를 달아서 dnd를 구현한다.

draggable 속성을 부여하면 해당 DOM은 draggable이 된다.

반면 droppable 속성은 별도로 없으며 onDragEnter로 특정 draggble이 들어왔는지, 그리고 onDragEnd로  놓아졌는지를 체크하는 식으로 구현한다.

 

dnd의 논리는 다음과 같다. pure javascript dnd 관련 검색해보면 많은 예시들이 있으니 참고하자.

 

1)  draggable의 onDragStart 이벤트를 통해 어떤 draggable이 움직이고 있는지 파악한다.

2) onDragEnter 속성이 있는 DOM위에 draggable이 올라간다면,

  2-1) 실시간으로 옮겨지는 결과를 보여주고 싶다면 onDragEnder에서 순서 state를 바꾼다. (대부분)

  2-2) 시각적으로 옮겨지지 않아도 상관 없다면 별다른 처리를 해주지 않아도 된다. (거의 없을 것으로 예상)

3) 실제로 drop하면 onDragEnd 이벤트가 발생한다. 

  3-1) draggable의 순서를 이미 onDragEnter에서 조정해주었다면 딱히 할 작업이 없다. React와 같은 SPA를 사용한다면 isDragging 상태를 지워준다던가, 이벤트 핸들러를 삭제해준다던가 하면 된다.

 

여기서 주의할 점은, setState를 통해 카드가 이동될 때마다 렌더되지 않도록 하자.

예를 들어 아래와 같은 속성을 가진 카드가 존재한다면, 각 컴포넌트(Card)마다 고유한 속성을 state로 관리하는 것이 아니라 전체 순서를 배열로 다루고 있다가, 변화가 일어나면, 배열의 순서에 맞게 Card를 다시 재배치(재렌더링)하는 것이 효율적이다.

// draggable에 현혹되지 말고 뒤에 있는 data가 중요함
const [ data, setData ] = useState([
  { id: 1, title: "lala", content: "haha" },
  { id: 2, title: "wawa", content: "whawha" },
  ...
])

<Draggable state={ ... } />

 

ref code) https://github.com/asatraitis/react-hooks-dragndrop

 

 

React-dnd

https://react-dnd.github.io/react-dnd/about

이번에 주로 사용해 볼 라이브러리이다. 회사에서 쓴다...

 

사용 전 overview를 읽어볼 것을 추천한다고 한다. 건질만한 내용을 정리해보자면 다음과 같다.

 

1. React DnD uses Redux internally

2. Data의 조작의 결과로 View가 바뀌는 것이지, View를 중심으로 생각해선 안된다. 데이터 중심으로 dnd를 이해할 것

3. moniter와 collect 함수 => react-dnd의 고유 개념으로, dnd 이벤트에 따라 변화되는 state를 반환합니다. 어, 그러니까 useDrag, useDrop이 반환하는 배열의 첫 인자로 들어옵니다. 

function collect(monitor) {
  return {
    highlighted: monitor.canDrop(),
    hovered: monitor.isOver()
  }
}
import { useDrag, useDrop } from 'react-dnd'

function DraggableComponent(props) {
  // useDrag의 첫 인자로 들어오게됨. collected가 그러함.
  const [collectedProps, drag, dragPreview] = useDrag(() => ({
    type,
    item: { id }
  }))
  return collectedProps.isDragging ? (
    <div ref={dragPreview} />
  ) : (
    <div ref={drag} {...collected}>
      ...
    </div>
  )
}

function myDropTarget(props) {
  // collectedProps로 넘어오게 됨.
  const [collectedProps, drop] = useDrop(() => ({
    accept
  }))

  return <div ref={drop}>Drop Target</div>
}

 

4. Connectors는 쉽게 말해 그냥 predefined된 역할이고, DOM에게 부여할 수 있습니다. 보통 useDrag, useDrop의 2번째 인자로 넘어오는 DragSource Ref, DropTarget Ref 를 DOM의 ref로 잡아주면 됩니다.

5. drag source(draggable), drop targets(droppable)은 react-dnd의 가장 기본적인 유닛.

 

그래서 구체적으로 어떻게 쓰는가?


... 작성중

 

Provider setting

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import { DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'

ReactDOM.render(
  <DndProvider backend={HTML5Backend}>
    <App />
  </DndProvider>,
  document.getElementById('root')
)

 

(1) useDrag

https://react-dnd.github.io/react-dnd/docs/api/use-drag

 

생각보다 간단합니다.

import { useDrag } from 'react-dnd'

function DraggableComponent(props) {
  const [collected, drag, dragPreview] = useDrag(() => ({
    type,
    item: { id }
  }))
  
  
  return collected.isDragging ? (
    <div ref={dragPreview} />
  ) : (
    <div ref={drag} {...collected}>
      ...
    </div>
  )
}

 

 

 

(2) useDrop

 

 

 

const [collectedProps, drop] = useDrop(() => ({
  accept:
  option:
  
}))

 

 

 

 

 

 

 

 

 

beautiful-dnd

예전에 써놓은 글이 있음. 강의도 있던데, 꽤나 유용했음.

https://darrengwon.tistory.com/1052