前言
前陣子在做 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 👍。
事前準備
為了方便 Demo,這次使用的是 Codesandbox,如果各位想要一邊看文章一邊實作,可以點選並進入下方的 Codesandbox,裡面已經包含這次所需要的套件,以及沒有任何功能的樣板。
👌 如果都準備好的話,那我們就開始囉 !
設定 Drag & Drop 的區域
首先,我們必須要設定 Drag & Drop 的作用域,讓 react-dnd 知道我們要在這個地方進行 Drag & Drop,所以我們在專案的最上層設定 DndProvider
把 TaskBoard
包起來。接著,同時在 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 件事:
- 撰寫 Card 托放到另一個 Column 的 function。:
- 為每個 Column 註冊
useDrop
的 connector function。
首先,我們在 TaskBoard.component.jsx
中加上 handleMoveMyTask
,並且把 handleMoveMyTask
傳遞到 Column 元件中。
要讓一個元件可以被 drop,我們要使用 useDrop
hook。useDrop
必須認識要被 drop 的元件是誰,所以要使用我們在之前撰寫的 ItemTypes.Card
,並且在 useDrop
的 accept
中指定允許的對象是 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 件事改變拖曳元件的外表:
- 撰寫客製化的 useDragLayer。
- 在
TaskBoard.component.jsx
放入 useDragLayer 元件。 - 讓原本拖曳時會產生的半透明元件消失。
這個 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中取得 canDrop
與 isOver
,再利用這兩個值就可以判斷是不是要在每個 Column 元件的最後渲染一個灰色的 Card
。
結論
這篇文章帶大家用 react-dnd 實做了一個任務牆,我們使用了 useDrag 讓元件可以被拖曳,使用 useDrop 讓元件可以被托放,使用 useDragLayer 修改原本拖曳元件的顯示模樣。
如果大家有興趣,也可以玩玩看 🔗 官方文件的 Tutorial,教你做出一個簡易的西洋棋拖曳功能。
分享就到這邊,如果喜歡我的文章可以幫我拍個幾下手,在閱讀文章時如果有遇到什麼問題,或是有什麼建議,都歡迎留言告訴我,謝謝。 😃