react拖拽功能實(shí)現(xiàn)

因項(xiàng)目中有拖拽功能需求,于是乎在github上找到了react-beautiful-dnd這個(gè)react列表拖拽庫(kù)幫助我們實(shí)現(xiàn)甬道間拖拽,下面介紹一下react-beautiful-dnd基本的幾個(gè)API和實(shí)現(xiàn)方法。

DragDropContext

拖拽上下文。可拖拽的內(nèi)容需包裹在DragDropContext中,DragDropContext不支持嵌套。

Props
type Hooks = {|
  // optional
  onDragBeforeStart?: OnDragBeforeStartHook,
  onDragStart?: OnDragStartHook,
  onDragUpdate?: OnDragUpdateHook,
  // required
  onDragEnd: OnDragEndHook,
|};

type OnBeforeDragStartHook = (start: DragStart) => mixed;
type OnDragStartHook = (start: DragStart, provided: HookProvided) => mixed;
type OnDragUpdateHook = (update: DragUpdate, provided: HookProvided) => mixed;
type OnDragEndHook = (result: DropResult, provided: HookProvided) => mixed;
  
type Props = {|
  ...Hooks,
  children: ?Node,
|};
基本用法
import { DragDropContext } from 'react-beautiful-dnd';

class App extends React.Component {
  onDragStart = () => {
    /*...*/
  };
  onDragUpdate = () => {
    /*...*/
  }
  onDragEnd = () => {
    // the only one that is required
  };

  render() {
    return (
      <DragDropContext
        onDragStart={this.onDragStart}
        onDragUpdate={this.onDragUpdate}
        onDragEnd={this.onDragEnd}
      >
        <div>Hello world</div>
      </DragDropContext>
    );
  }
}

Droppable

Droppable為放置拖拽元素的甬道,< Draggable/>必須包裹在<Droppable/>中。

Props
import type { Node } from 'react';

type Props = {|
  // required
  droppableId: DroppableId, // 必需,可拖動(dòng)甬道的唯一標(biāo)識(shí)
  // optional
  type?: TypeId, // string,用來(lái)簡(jiǎn)單的接受某一類(lèi)draggable,當(dāng)兩個(gè)droppable的type值一樣時(shí),甬道內(nèi)的draggable才能互相拖動(dòng)
  mode?: DroppableMode, // 拖動(dòng)模式,默認(rèn)為standard(標(biāo)準(zhǔn))模式,另一種模式為處理大量數(shù)據(jù)的virtual(虛擬)模式
  isDropDisabled?: boolean, // 用于控制拖動(dòng)起來(lái)的draggable是否允許放到當(dāng)前Droppable,默認(rèn)為false(允許)
  isCombineEnabled?: boolean, // 是否允許draggable合并,默認(rèn)false
  direction?: Direction,  // 可拖拽塊在droppable上的移動(dòng)方向,甬道為垂直的就為vertical(默認(rèn)),水平的為horizontal
  ignoreContainerClipping?: boolean,
  renderClone?: DraggableChildrenFn, // virtual模式中需使用
  getContainerForClone?: () => HTMLElement,
  children: (DroppableProvided, DroppableStateSnapshot) => Node,
|};

type DroppableMode = 'standard' | 'virtual';
type Direction = 'horizontal' | 'vertical';

// DraggableChildrenFn: 需返回一個(gè)ReactElement
<Droppable droppableId="droppable-1">
  {(provided, snapshot) => ({
    /*...*/
  })}
</Droppable>;
placeholder

通常,我們需要將placeholder(<Droppable /> | DroppableProvided | placeholder)放入列表中,以便在拖動(dòng)過(guò)程中根據(jù)需要在列表中插入空格。

<Droppable droppableId="droppable">
  {(provided, snapshot) => (
    <div ref={provided.innerRef} {...provided.droppableProps}>
      {/* Usually needed. But not for virtual lists! */}
      {provided.placeholder}
    </div>
  )}
</Droppable>

Tips: 在虛擬列表中我們不需要加入placeholder占位符,因?yàn)樘摂M列表中我們不是基于可視項(xiàng)的集合大小來(lái)確定列表的尺寸,而是根據(jù)itemCount來(lái)計(jì)算的。(height = itemSize*itemCount)。對(duì)于虛擬列表,將我們自己的節(jié)點(diǎn)插入其中不會(huì)增加列表的大小。

Draggable

Draggable 為可拖拽的塊,<Draggable/>必須放在<Droppable/>里。

Props
import type { Node } from 'react';

type Props = {|
  // required
  draggableId: DraggableId, // 可拖拽塊的唯一標(biāo)識(shí)id
  index: number, // index索引
  children: DraggableChildrenFn,
  // optional
  isDragDisabled: ?boolean, // 是否允許該draggable被拖動(dòng)
  disableInteractiveElementBlocking: ?boolean,
  shouldRespectForcePress: ?boolean,
|};
基本用法
const getItems = count =>
  Array.from({ length: count }, (v, k) => k).map(k => ({
  id: `item-${k}`,
  content: `item-${k}`
}))

const grid = 8;

const getItemStyle = (isDragging, draggableStyle) => ({
  // some basic styles to make the items look a bit nicer
  userSelect: "none",
  padding: grid * 2,
  margin: `0 0 ${grid}px 0`,

  // change background color if dragging
  background: isDragging ? "lightgreen" : "grey",

  // styles we need to apply on draggables
  ...draggableStyle
});

const getListStyle = isDraggingOver => ({
  background: isDraggingOver ? "lightblue" : "lightgrey",
  padding: grid,
  width: 250
});

<DragDropContext onDragEnd={onDragEnd}>
   <Droppable droppableId="drop">
      {(provided, snapshot) => (
         <div
           {...provided.droppableProps}
           ref={provided.innerRef}
           style={getListStyle(snapshot.isDraggingOver)}
          >
           {getItems(10).map((item, index) => (
              <Draggable key={item.id} draggableId={item.id} index={index}>
                {(provided, snapshot) => (
                  <div
                    ref={provided.innerRef}
                    {...provided.draggableProps}
                    {...provided.dragHandleProps}
                    style={getItemStyle(
                      snapshot.isDragging,
                      provided.draggableProps.style
                    )}
                  >
                    {item.content}
                  </div>
                )}
              </Draggable>
            ))}
            {provided.placeholder}
          </div>
        )}
      </Droppable>
    </DragDropContext>

拖拽圖示

拖拽圖示

virtual模式

當(dāng)數(shù)據(jù)量足夠大的時(shí)候,相應(yīng)地渲染出來(lái)的dom也會(huì)足夠的多, react-virtualized便是一個(gè)react長(zhǎng)列表解決方案。

react-beattiful-dnd@12.0版本也增加了對(duì)虛擬列表的支持。

虛擬列表原理

虛擬列表通過(guò)判斷并只加載當(dāng)前視窗內(nèi)的列表元素來(lái)解決海量數(shù)據(jù)列表。

  1. 自行引入 react-virtualizedreact-window,使用他們的一些支持虛擬列表的組件。

  2. 首先要把<Droppable/>的mode屬性設(shè)為virtual(參考上文Droppable的props),告訴DragDropContext當(dāng)前甬道為虛擬列表模式。

  3. 使用<Droppable/>renderCloneAPI 。在虛擬列表模式下,拖動(dòng)時(shí)原始<Draggable/>會(huì)被刪除,然后用renderClone克隆個(gè)新的放到容器元素中。

renderClone用法:

function List(props) {
  const items = props.items;

  return (
    <Droppable
      droppableId="droppable"
      renderClone={(provided, snapshot, rubric) => (
        <div
          {...provided.draggableProps}
          {...provided.dragHandleProps}
          ref={provided.innerRef}
        >
          Item id: {items[rubric.source.index].id}
        </div>
      )}
    >
      {provided => (
        <div ref={provided.innerRef} {...provided.droppableProps}>
          {items.map(item) => (
            <Draggable draggableId={item.id} index={item.index}>
              {(provided, snapshot) => (
                <div
                  {...provided.draggableProps}
                  {...provided.dragHandleProps}
                  ref={provided.innerRef}
                >
                  Item id: {item.id}
                </div>
              )}
            </Draggable>
          )}
        </div>
      )}
    </Droppable>
  );
}

const getRenderItem = (items) => (provided, snapshot, rubric) => (
  <div
    {...provided.draggableProps}
    {...provided.dragHandleProps}
    ref={provided.innerRef}
  >
    Item id: {items[rubric.source.index].id}
  </div>
);

function List(props) {
  const items = props.items;
  const renderItem = getRenderItem(items);

  return (
    <Droppable
      droppableId="droppable"
      renderClone={renderItem}
    >
      <div ref={provided.innerRef} {...provided.droppableProps}>
        {items.map(item) => (
          <Draggable draggableId={item.id} index={item.index}>
            {renderItem}
          </Draggable>
        )}
      </div>
    </Droppable>
  );
}

Tips: 在使用react-virtualized時(shí),稍不注意會(huì)出現(xiàn)滾動(dòng)出第一屏后頁(yè)面閃爍的問(wèn)題。

react-virtualized使用注意事項(xiàng)

拖動(dòng)的原理——數(shù)組的重排

onDragEnd

該鉤子是拖拽過(guò)程中最重要的一個(gè)函數(shù),也是必需的,該函數(shù)必須導(dǎo)致列表數(shù)據(jù)的重新排序。它也提供來(lái)有關(guān)拖動(dòng)的所有信息。

result:DropResult
type DropResult = {|
  ...DragUpdate,
  reason: DropReason,
|}

type DropReason = 'DROP' | 'CANCEL';
  • result.draggableId: 拖動(dòng)的draggabledraggableId
  • result.type: 拖動(dòng)的draggable的類(lèi)型(type),droppable上設(shè)置的type值
  • result.source: draggable的起始位置(包含起始位置的index索引和droppableId)
  • result.destination: draggable完成的位置,如果用戶在超過(guò)<Droppable/>的情況下掉落,則目標(biāo)將為null(如果不為null,則包含結(jié)束位置的index索引和droppableId)
  • result.reason: 下降的原因
你需要做的
  • 如果result.destination為null,直接return;
  • 如果source.droppableIddestination.droppableId相等,則需要從列表中刪除該項(xiàng)目并放置到正確的位置;
  • 如果source.droppableIddestination.droppableId不相等,則需要source.droppableId列表中刪除該項(xiàng)目并放置到destination.droppableId正確的位置;

附上我的demo代碼庫(kù),有興趣的可以看看。demo代碼庫(kù)
另附上react-beautiful-dnd官方地址。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容