React DnD简明教程
React DnD簡明教程
概述
React Dnd不同于其他的拖拽庫,如果你以前沒有用過它可能會被嚇到。然而,一旦你了解了它設計的一些核心概念,它將變得有意義。我建議你在閱讀文檔其他部分之前,先閱讀這些核心概念。
這些核心概念和flux/redux架構相似。這并不是巧合,因為React DnD內部就是用的Redux。
Backends(后端)
React DnD是基于HTML5的拖放API搭建的。基本原因是這個API能夠截取拖拽的dom節點作為一個拖拽預覽。它的便利性就在于你不必為拖拽時的鼠標繪制樣式。這個API也是處理文件拖拽上傳的唯一方式。
不幸的是,HTML5拖拽API也有缺點。它不支持觸摸屏,并且它在IE上提供的可定制機會要少于其他瀏覽器。
這就是為什么React DnD把HTML5的拖拽支持作為一種可插拔的方式來實現。你不必用它。你可以自己做一個不同的實現,基于觸摸事件,鼠標事件,甚至完全不同的東西。一些可插拔的(拖拽接口)實現在React DnD被稱作“backends”。React DnD目前僅提供了HTML5 backends,但是以后可能會添加更多的實現。
Backends承擔的角色有點像React的事件合成系統:他們都抽象了瀏覽器的事件和DOM事件實現的區別。雖然有相似點,React DnD的backends卻并不依賴React或事件合成系統。在底層,backends做的事情就是把DOM事件轉換成React DnD可以處理的Redux actions。
Items and Types(項和類型)
像Flux(或Redux),React DnD用數據作為真實的處理源,而不是界面。當你在屏幕上拖動一些東西時,我們不說是一個組件或者一個DOM節點被拖動了。事實上,我們說是某個特定的type(類型)的item(項)被拖動了。
什么是item?Item就是一個純js對象,用以描述什么被拖動了。舉個例子,在看板應用中,當你拖動一個卡片,item看起來可能像{cardId:42}。在一個象棋游戲中,當你撿起一個棋子,item看起來可能像{fromCell: 'C5', piece: 'queen'}。用純對象描述拖拽數據,可以幫助你解耦組件和使組件間互無干擾。
什么是type?type是一個字符串(或一個symbol)在應用中唯一標識items的完整類型。在看板應用中,可能有一個“card”類型代表可拖動的卡片,同時有一個“list”類型用以表示由這些卡片組成的可拖動的列表。在象棋應用中,可能只有一個“piece”類型。
Types(類型)是有用的,隨著你應用的增長,你可能想讓更多東西可拖動,但是你不一定想放置目標突然接受新的items(項)。Types能讓你指定哪些拖拽源和放置目標可兼容。你可能會枚舉出所有的type常量,就像枚舉Redux的action types一樣。
Monitors(監控器)
拖拽本身是有狀態的。要么拖動操作正在進行,或者沒有。要么有一個類型和一個項,或者沒有。狀態一定存在于某處。
React DnD通過一些被稱作monitors的包裝了內部狀態存儲的封裝器把這些狀態暴露給組件。這些封裝器讓你更新組件props來響應拖拽狀態的改變。
每個組件都需要跟蹤拖放狀態,你可以定義一個收集函數來獲取從監控器返回的一些細節。React DnD負責及時調用收集函數將返回結果合并到你的組件props中。
比如說你想在某個棋子被拖動后高亮棋盤格子。為Cell組件定義的收集函數可能像這樣:
function collect(monitor) {return { highlighted:monitor.canDrop(),hovered: monitor.isOver() }; }這個函數會指示React DnD傳遞最新的highlighted和hovered的值給所有的Cell組件實例的屬性。
Connectors (連接器)
如果backend掌控的是DOM事件,而組件用的是React(虛擬DOM)描述DOM,那么backend怎么知道哪些DOM節點應該被監聽勒?通過輸入connectors。連接器讓你在render函數中分配一條預定規則(拖拽源,拖拽預覽,放置目標)給DOM節點。
事實上,connector作為第一個參數被傳遞給我們上面提到的收集函數。我們來看看怎樣用它指定一個放置目標:
function collect(connect, monitor){return{highlighted:monitor.canDrop(),hovered: monitor.isOver(), connectDropTarget: connect.dropTarget()}; }在組件的render方法中,能同時獲得從監控器放回的數據,和從連接器返回的函數。
render(){const { highlighted, hovered, connectDropTarget } = this.props; return connectDropTarget(<div className={classSet({'Cell': true,'Cell--highlighted': highlighted, 'Cell--hovered': hovered })}> {this.props.children} </div>); }connectDropTarget告訴React DnD組件的根節點是一個有效的放置目標,并且backend應該掌控它的hover和drop事件。在內部它通過你指定的React元素的引用回調來工作。這個函數通過連接器memoized后返回的,所以它不會中斷shouldComponentUpdate 的優化。
Drag Sources and Drop Targets(拖拽源和放置目標)
截止目前我們已經介紹了和DOM打交道的backends,代表項和類型的數據,以及收集函數,得益于監控器和連接器,你能描述React DnD應該將哪些props注入到組件中。
但是我們如何配置組件來實際接受這些注入的props呢?我們怎樣執行具有副作用的拖放事件呢?是時候來會會drag sources(拖拽源)和drop targets(放置目標)了,這是React DnD主要的抽象單位。它們才是真正把類型、項、副作用操作,和收集函數連接到你組件中的東西。
無論何時你想讓你的組件或者它的一部分可拖動,你都需要用拖拽源聲明來包裝這個組件。每個拖拽源都需要注冊一個特定的類型,并且必須實現一個通過組件props生成項的方法。它也可以選擇性的指定其他一些方法來處理拖放事件。拖放源聲明還允許你為給定組件指定收集函數。
放置目標和拖拽源十分相似。唯一的區別在于一個放置目標可以同時注冊幾個項類型,而不是提供一個項,它能掌控自己的hover和drop事件。
Higher-Order Components and ES7 decorators (高階組件和ES7修飾器)
你怎么包裝你的組件?包裝糾結是什么意思?如果你以前沒有用過高階組件,先讀讀這篇文章,它詳細介紹了這個概念。
所謂高階組件就是一個函數,獲取一個React組件類并返回另一個不同的組件類。
The wrapping component provided by the library renders your component in its render method and forwards the props to it, but also adds some useful behavior.(這句目前不知道怎么翻譯,只好原文引用了。)
在React DnD中,DragSource和DropTarget,以及其他的一些頂級的對外函數,實際上都是高階組件。他們把拖放魔法帶入到你的組件里。
一個關于使用它們的警告就是他們需要兩個應用函數。舉個例子,這里是如何用DragSource包裝YourComponent:
import { DragSource } from 'react-dnd'; class YourComponent { /* ... */ } export default DragSource(/* ... */)(YourComponent);注意,在指定DragSource的參數的時候發生了第一次調用,然后再最后傳遞你的組件類的地方,發生了第二次調用。這被稱為函數柯里化,或者偏函數用法,并且有必要創造性的使用ES7的修飾符語法。
你不必使用這個語法,但是如果你喜歡,你就能用Babel轉換你的代碼,并且在.babelrc文件里設置{ "stage": 1 }。
即使你不打算用ES7,偏函數用法也是依然有益的,因為它可以幫你在ES5或ES6中用有合并功能的輔助工具如 _.flow,合并幾個DragSource 和 DropTarget 聲明。在ES7中,你只需堆疊使用這些修飾符就能達到同樣效果。
import { DragSource } from 'react-dnd';@DragSource(/* ... */) @DropTarget(/* ... */) export default class YourComponent {render() {const { connectDragSource, connectDropTarget } = this.propsreturn connectDragSource(connectDropTarget(/* ... */))} }Putting It All Together (總結)
下面是一個包裝現有Card組件作為拖拽源的例子。
import React from 'react'; import { DragSource } from 'react-dnd';// Drag sources and drop targets only interact // if they have the same string type. // You want to keep types in a separate file with // the rest of your app's constants. const Types = {CARD: 'card' };/*** Specifies the drag source contract.* Only `beginDrag` function is required.*/ const cardSource = {beginDrag(props) {// Return the data describing the dragged itemconst item = { id: props.id };return item;},endDrag(props, monitor, component) {if (!monitor.didDrop()) {return;}// When dropped on a compatible target, do somethingconst item = monitor.getItem();const dropResult = monitor.getDropResult();CardActions.moveCardToList(item.id, dropResult.listId);} };// Use the decorator syntax @DragSource(Types.CARD, cardSource, (connect, monitor) => ({// Call this function inside render()// to let React DnD handle the drag events:connectDragSource: connect.dragSource(),// You can ask the monitor about the current drag state:isDragging: monitor.isDragging() })) export default class Card {render() {// Your component receives its own props as usualconst { id } = this.props;// These two props are injected by React DnD,// as defined by your `collect` function above:const { isDragging, connectDragSource } = this.props;return connectDragSource(<div>I am a draggable card number {id}{isDragging && ' (and I am being dragged now)'}</div>);} }現在你已經掌握了足夠的信息去探索文檔余下的部分了。
這個實例將是一個不錯的開始。
參考鏈接:https://liunianmou.gitbooks.io/react-dnd/content/chapter1.html
總結
以上是生活随笔為你收集整理的React DnD简明教程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: win11 通过winget安装/卸载v
- 下一篇: 【工具分享】deepin v20.5桌面