React DnD | 實現可拖曳的任務牆

你今天 Drag & Drop 了嗎 ?

Leo Chiu
手寫筆記
8 min readNov 21, 2019

--

Photo by Hugo Rocha on Unsplash

前言

前陣子在做 side project 與前端精神修時光屋的接龍遊戲都用到了 drag and drop 的功能,如果對接龍遊戲有興趣可以參考 👉 Github 連結。這次想帶大家實作一個像似 Trello 看板卡片拖曳的功能,讓大家也能作出屬於自己的任務牆。

React 常見的兩個 Drag & Drop 套件有 react-beautiful-dnd 和 react-dnd,雖然 react-dnd 的星星數較少,但是下載使用量較高,所以使用 react-dnd 做為實作的主要套件。

不過 react-beautiful-dnd 的 🔗官方範例非常的豐富,看起來也真的比 react-dnd 還要漂亮有機會再來玩玩看 react-beautiful-dnd 👍。

Codesandbox — 任務牆完成品

事前準備

為了方便 Demo,這次使用的是 Codesandbox,如果各位想要一邊看文章一邊實作,可以點選並進入下方的 Codesandbox,裡面已經包含這次所需要的套件,以及沒有任何功能的樣板。

👌 如果都準備好的話,那我們就開始囉 !

設定 Drag & Drop 的區域

首先,我們必須要設定 Drag & Drop 的作用域,讓 react-dnd 知道我們要在這個地方進行 Drag & Drop,所以我們在專案的最上層設定 DndProviderTaskBoard 包起來。接著,同時在 DndProvider 上設定 HTML5backend

HTML5backend 有些好用的功能,會在專案的後半段使用到。

定義 Drag & Drop 的元件屬性

Drag & Drop 要認識被拖曳的元件是誰 (是誰 ?),所以可以在 Constants.js 中定義我們會用到的拖曳元件名稱 CARD ,我們會在設定 Drag 或是 Drop 時用到它。

以上兩點就是 Drag & Drop 的基本設定,接下來我們就可以來寫拖曳的事件了。

使用 useDrag 讓 Card 可以被拖曳

想要讓 Card 可以被拖曳,我們需要定義 useDrag hook,在 useDrag 裡面的 item,我們需要設定剛剛定義的 ItemTypes.Card。然後把回傳的第二個變數 dragRef 綁定到 div 上,如此一來就可以讓 Card 被拖曳。

useDrag 這個 API 會回傳一個陣列,這個陣列總有有 3 個元素:

  • Inedx 0: 這個物件包含了在 useDrag 中的 collect function 回傳的屬性,如果沒有 collect function 的話,會回傳一個空物件。
  • Index 1: 這是一個註冊 drag 元件的 connector function,它被必須寫在我們要 drag 的元件 DOM 上。
  • Index 2: 這是一個能夠改變 drop 元件顯示的 connector function,同樣的,它也必須寫在我們要拖曳的元件 DOM 上。(在後半段會介紹)

想看 useDrag 更詳細的介紹可以閱讀 🔗官方文件

使用 useDrop 讓 Card 可以被托放到另一個區域

👉為了讓 Card 可以托放到另一個不同的 Column,我們必須做 2 件事:

  1. 撰寫 Card 托放到另一個 Column 的 function。:
  2. 為每個 Column 註冊 useDrop 的 connector function。

首先,我們在 TaskBoard.component.jsx 中加上 handleMoveMyTask,並且把 handleMoveMyTask 傳遞到 Column 元件中。

要讓一個元件可以被 drop,我們要使用 useDrop hook。useDrop 必須認識要被 drop 的元件是誰,所以要使用我們在之前撰寫的 ItemTypes.Card,並且在 useDropaccept 中指定允許的對象是 ItemTypes.Card

接著,我們要在 Card 被 drop 後,可以把 Card 從原本的 Column 中刪除,並起把 Card 放到 drop 的 Column 上。所以,我們在 useDrop 中的 drop: () => {} 放入在 drop 後會執行的事件 handleMoveMyTask

useDrop 的回傳值是一個陣列,這個陣列共有兩個元素:

  • Index 0: 這個物件包含了在 useDrag 中的 collect: () => {}回傳的屬性,如果沒有 collect function 的話,會回傳一個空物件。
  • Index 1: 這是一個註冊 drop 元件的 connector function,它被必須寫在我們要 drop 的元件 DOM 上。

想看 useDrop 更詳細的介紹可以閱讀 🔗官方文件

使用 useDragLayer 客製化拖曳圖示

可是你會想說,這個拖曳的 Card 是半透明的,感覺不是很好看,有沒有辦把讓它變成不是半透明的。 React DnD 提供了改變拖曳元件狀態的接口 useDragLayer,讓我們自己定義拖曳的元件長什麼樣子。

👉 接下來我們要做 3 件事改變拖曳元件的外表:

  1. 撰寫客製化的 useDragLayer。
  2. TaskBoard.component.jsx 放入 useDragLayer 元件。
  3. 讓原本拖曳時會產生的半透明元件消失。

這個 useDragLayer 的範例大家可以參考官方文件中的 🔗example — Custom Drag Layer

接著,我們在 TaskBoard.component.jsx中加上剛剛寫的 CustomDragLayer,就可以在拖曳時加上客製化的樣式。

加上 CustomDragLayer 後終於完成了,可是你發現怎麼有倩女幽魂 👻 跟在拖曳的元件後方。不要緊張,只是你還沒斷開跟原本預設拖曳元件樣式的連結,所以我們可以使用原本在 HTML5backend 的功能 getEmptyImage,如此一來我們就可斷開魂結了。

還記得我們在介紹 useDrag 時有提到,它的回傳值有 3 個,而第 3 個即是拖曳元件的接口。所以我們可以在 DraggableCard.component.jsx 中設定 preview 的元件為 getEmptyImage

使用 collect function 製作 Trello 的 hover 樣式

有時候我們怎麼知道被 drag 的元件有沒有 hover 在 drop 的元件上呢 ? 記得在使用 Trello 時,當 Card 被拖曳到另一個 Column 上時,都會有一個灰色的卡片生成,這個要怎麼做呢 ?

這時我們可以 collection function中取得 canDropisOver ,再利用這兩個值就可以判斷是不是要在每個 Column 元件的最後渲染一個灰色的 Card

結論

這篇文章帶大家用 react-dnd 實做了一個任務牆,我們使用了 useDrag 讓元件可以被拖曳,使用 useDrop 讓元件可以被托放,使用 useDragLayer 修改原本拖曳元件的顯示模樣。

如果大家有興趣,也可以玩玩看 🔗 官方文件的 Tutorial,教你做出一個簡易的西洋棋拖曳功能。

https://react-dnd.github.io/react-dnd/docs/tutorial

Reference

--

--

Leo Chiu
手寫筆記

每天進步一點點,在終點遇見更好的自己。 Instragram 小帳:@leo.web.dev