
React-Beautiful-DnD 是由 Atlassian 開發,也就是 Trello、Slack、Jira 的母公司,但這邊要澄清一下,雖然 Trello 擁有使用者體驗很好的拖曳功能,但 Trello 是 2017 年被 Atlassian 收購,所以不是用 React-Beautiful-DnD 開發的喔!回到 React-Beautiful-DnD ,它提供 React 開發者更簡易的實現列表拖曳功能,主要使用 CSS 和 React實作動畫及交互作用,在美觀及功能上都提升開發者體驗。
<DragDropContext /> - 想要進行 drag and drop 的範圍,<Droppable /> 和 <Draggable /> 的 wrapper,類似 provider 的功能<Droppable /> - 可執行 drop 的範圍。 可包含許多 <Draggable />
<Draggable /> - 可被拖移的原件範圍resetServerContext() - 提供給 SSR 使用<DragDropContext />onDragEnd - 必要屬性onDragStart - 拖曳行為開始時觸發onDragUpdate - 拖曳行為讓順序產生變動時觸發onBeforeCapture - 在拖動操作即將開始之前,尚未從網頁的 DOM 中獲取相關元素的尺寸資訊onBeforeDragStart - 在拖動操作即將開始之前,已從網頁的 DOM 中獲取相關元素的尺寸資訊children
dragHandleUsageInstructions - 當焦點聚焦在拖曳 handler 上時,透過螢幕閱讀器朗讀出來的內容。nonce - 用於嚴格的內容安全策略sensors - 提供客製化的 sensor 設置enableDefaultSensors
套件中主要的操作功能,可以在這個 function 中處理拖曳後的結果,因為會接收一個拖曳後的列表資訊,例如哪個元素被拖動、它原本在哪裡、被放到了哪裡等等。
屬性:
source - 被拖曳 item 的 droppableId 及原本的 indexdestination - 被拖曳 item 的 droppableId 及目的地位置的 indexcombine - 當 <Droppable/> 中 isCombineEnabled 屬性被設為 true 時可調用的 flag<Droppable />希望使用者可以將元件拖拽到哪個位置範圍,可以使用
<Droppable />
為一個頭尾標籤,其中必填屬性為 droppableId 及一個包含 provided 和 snapshot 參數的 children。
範例:
import { Droppable } from 'react-beautiful-dnd';
<Droppable droppableId="droppable-1" type="PERSON">
{(provided, snapshot) => (
<div
ref={provided.innerRef}
style={{ backgroundColor: snapshot.isDraggingOver ? 'blue' : 'grey' }}
{...provided.droppableProps}
>
<h2>I am a droppable!</h2>
{provided.placeholder}
</div>
)}
</Droppable>;
droppableId - 必填屬性,為識別 Droppable component 的唯一 Id
包含 provided 和 snapshot 參數,並回傳一個 reactElement
provided.innerRef - 必設,設定給 reactElement 的 refprovided.droppableProps - 以解構方式設定於 reactElement,用於樣式和查找的資料屬性provided.placeholder - 使拖曳時有保留該元素的空間isDraggingOver - 判斷 <Droppable /> 中的 <Draggable /> 元素是否正在被拖曳draggingOverWith - 正在 <Droppable /> 上被拖曳的元素 iddraggingFromThisWith - 在列表中拖曳的元素 id ,可針對尚未進行拖曳時的列表做樣式設定。isUsingPlaceholder - 在 virtual 模式時,可判斷 placeholder 是否正在被使用。<Draggable />
Draggable組件代表了可以被拖拽的項目,必須被包在Droppable的 children 中。
<Droppable droppableId="droppable-1">
{(provided, snapshot) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
<Draggable key={item?.id} draggableId={item?.id} index={index}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<MdDragIndicator size={24} className="text-grey-400" />
<DisplayLinkItem />
</div>
)}
</Draggable>
{provided.placeholder}
</div>
)}
</Droppable>;
為一個頭尾標籤,其中必填屬性為 draggableId 、index 及一個包含 provided 和 snapshot 參數的 children。
draggableId - 必填屬性,為識別各個 Draggable component 的唯一 Idindex - 必填屬性,onDragEnd 調用時的列表順序判斷依據key - React Element 必填屬性包含 provided 和 snapshot 參數,並回傳一個 React Element
provided
provided.innerRef - 必設,設定給各個 React Element 的 refprovided.droppableProps - 以解構方式設定於 reactElement,用於樣式和查找的資料屬性
style - droppable 組件的樣式onTransitionEnd
provided.dragHandleProps - 以解構方式設定於執行拖曳行為的物件上,包含 tabIndex 、draggable 、onDragStart 屬性<div
ref={provided.innerRef}
{...provided.draggableProps} // 被拖曳的組件
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}
>
<div {...provided.dragHandleProps}> // 只有在這個元素上可執行拖曳動作
<DragHandleIcon />
</div>
<ScheduleItem
index={index}
formik={formik}
handleDelete={handleDelete}
/>
</div>

snapshot - 多使用於樣式設定
isDragging - 判斷該 Draggable 元素是否正在被拖曳isDropAnimating - 判斷是否正在進行拖曳動畫 (基本上用不到的屬性)dropAnimation - 當前被拖拽的元素正在懸停或拖曳至哪個放置目標,通常為 Droppable 的iddraggingOver - 當一個 Draggable 被放下時的動畫效果
在 React 18 的 Strict mode 下, useLayoutEffect 的行為會與之前的版本有所不同,特別是在 concurrent rendering 的環境下。react-beautiful-dnd 是在 useLayoutEffect 中註冊它的 droppables,如下範例:
useLayoutEffect(() => {
registry.draggable.register(publishedRef.current);
return () => registry.draggable.unregister(publishedRef.current);
}, [registry.draggable]);
依據 dependency array 中的設定,雖然在初次的 mount 進行時會觸發此 useLayoutEffect,但當元件在 StrictMode 下因 concurrent rendering 而進行 remount 操作時,useLayoutEffect 可能不會被再次觸發。這種行為可能導致 Droppable 和 Draggable 無法被完全渲染,並出現 unable to find draggable with id: 錯誤。
因此在 Strict mode 下的解決方法就是使用 useEffect 及 requestAnimationFrame,useEffect 是為了確保元件在瀏覽器渲染畫面後再渲染,requestAnimationFrame 是為了使其中的 callback function 在下一次瀏覽器重繪之前被執行,使得 Droppable 和 Draggable 被正確的渲染。
import { useEffect, useState } from "react";
import { Droppable, DroppableProps } from "react-beautiful-dnd";
export const StrictModeDroppable = ({ children, ...props }: DroppableProps) => {
const [enabled, setEnabled] = useState(false);
useEffect(() => {
const animation = requestAnimationFrame(() => setEnabled(true));
return () => {
cancelAnimationFrame(animation); // 組件卸載時取消請求的動畫幀,以防止內存洩漏
setEnabled(false);
};
}, []);
if (!enabled) {
return null;
}
return <Droppable {...props}>{children}</Droppable>;
};
兩年前官方已經宣布除了安全性的修正,現階段不會再進行其他維護更新,所以在日新月異的框架技術中會出現越來越多 issue,但基於方便性以及美觀性,使用者還是非常的多。為了解決上述的問題,社群上有熱心的開發者直接 fork 了原專案進行維護及修正,也將套件以 TypeScript 進行重構 - hello-pangea/dnd,使用方法跟原本的 React-Beautiful-DnD 一模一樣,只需要在 import 時改為 @hello-pangea/dnd,並且也修正了上面 React 18 的 issue。提供這個資訊給還是希望使用 React-Beautiful-DnD 但又因版本問題深陷苦惱的各位。

https://github.com/atlassian/react-beautiful-dnd/issues/2399#issuecomment-1137880007
https://github.com/hello-pangea/dnd
