react拖拽功能实现

作者: 汉堡会有的 | 来源:发表于2019-12-06 12:15 被阅读0次

    因项目中有拖拽功能需求,于是乎在github上找到了react-beautiful-dnd这个react列表拖拽库帮助我们实现甬道间拖拽,下面介绍一下react-beautiful-dnd基本的几个API和实现方法。

    DragDropContext

    拖拽上下文。可拖拽的内容需包裹在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, // 必需,可拖动甬道的唯一标识
      // optional
      type?: TypeId, // string,用来简单的接受某一类draggable,当两个droppable的type值一样时,甬道内的draggable才能互相拖动
      mode?: DroppableMode, // 拖动模式,默认为standard(标准)模式,另一种模式为处理大量数据的virtual(虚拟)模式
      isDropDisabled?: boolean, // 用于控制拖动起来的draggable是否允许放到当前Droppable,默认为false(允许)
      isCombineEnabled?: boolean, // 是否允许draggable合并,默认false
      direction?: Direction,  // 可拖拽块在droppable上的移动方向,甬道为垂直的就为vertical(默认),水平的为horizontal
      ignoreContainerClipping?: boolean,
      renderClone?: DraggableChildrenFn, // virtual模式中需使用
      getContainerForClone?: () => HTMLElement,
      children: (DroppableProvided, DroppableStateSnapshot) => Node,
    |};
    
    type DroppableMode = 'standard' | 'virtual';
    type Direction = 'horizontal' | 'vertical';
    
    // DraggableChildrenFn: 需返回一个ReactElement
    <Droppable droppableId="droppable-1">
      {(provided, snapshot) => ({
        /*...*/
      })}
    </Droppable>;
    
    placeholder

    通常,我们需要将placeholder(<Droppable /> | DroppableProvided | placeholder)放入列表中,以便在拖动过程中根据需要在列表中插入空格。

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

    Tips: 在虚拟列表中我们不需要加入placeholder占位符,因为虚拟列表中我们不是基于可视项的集合大小来确定列表的尺寸,而是根据itemCount来计算的。(height = itemSize*itemCount)。对于虚拟列表,将我们自己的节点插入其中不会增加列表的大小。

    Draggable

    Draggable 为可拖拽的块,<Draggable/>必须放在<Droppable/>里。

    Props
    import type { Node } from 'react';
    
    type Props = {|
      // required
      draggableId: DraggableId, // 可拖拽块的唯一标识id
      index: number, // index索引
      children: DraggableChildrenFn,
      // optional
      isDragDisabled: ?boolean, // 是否允许该draggable被拖动
      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模式

    当数据量足够大的时候,相应地渲染出来的dom也会足够的多, react-virtualized便是一个react长列表解决方案。

    react-beattiful-dnd@12.0版本也增加了对虚拟列表的支持。

    虚拟列表原理

    虚拟列表通过判断并只加载当前视窗内的列表元素来解决海量数据列表。

    1. 自行引入 react-virtualizedreact-window,使用他们的一些支持虚拟列表的组件。

    2. 首先要把<Droppable/>的mode属性设为virtual(参考上文Droppable的props),告诉DragDropContext当前甬道为虚拟列表模式。

    3. 使用<Droppable/>renderCloneAPI 。在虚拟列表模式下,拖动时原始<Draggable/>会被删除,然后用renderClone克隆个新的放到容器元素中。

    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时,稍不注意会出现滚动出第一屏后页面闪烁的问题。

    react-virtualized使用注意事项

    拖动的原理——数组的重排

    onDragEnd

    该钩子是拖拽过程中最重要的一个函数,也是必需的,该函数必须导致列表数据的重新排序。它也提供来有关拖动的所有信息。

    result:DropResult
    type DropResult = {|
      ...DragUpdate,
      reason: DropReason,
    |}
    
    type DropReason = 'DROP' | 'CANCEL';
    
    • result.draggableId: 拖动的draggabledraggableId
    • result.type: 拖动的draggable的类型(type),droppable上设置的type值
    • result.source: draggable的起始位置(包含起始位置的index索引和droppableId)
    • result.destination: draggable完成的位置,如果用户在超过<Droppable/>的情况下掉落,则目标将为null(如果不为null,则包含结束位置的index索引和droppableId)
    • result.reason: 下降的原因
    你需要做的
    • 如果result.destination为null,直接return;
    • 如果source.droppableIddestination.droppableId相等,则需要从列表中删除该项目并放置到正确的位置;
    • 如果source.droppableIddestination.droppableId不相等,则需要source.droppableId列表中删除该项目并放置到destination.droppableId正确的位置;

    附上我的demo代码库,有兴趣的可以看看。demo代码库
    另附上react-beautiful-dnd官方地址。

    相关文章

      网友评论

        本文标题:react拖拽功能实现

        本文链接:https://www.haomeiwen.com/subject/zcxlgctx.html