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