React 最佳实践:基于路由实现分步操做

React-Beautiful-DND,一个强大的拖拽包,可以优雅的作出丰富的拖拽页面应用,适用于列表之间拖拽的场景,支持移动端,且简单易上手。官方丰富的案例展现,正合心意。css

核心概念

01.gif

DragDropContext

import { DragDropContext } from 'react-beautiful-dnd';

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

  return (
    <DragDropContext onDragStart={onDragStart} onDragUpdate={onDragUpdate} onDragEnd={onDragEnd} > <div>Hello world</div> </DragDropContext>
  );
};
复制代码

构建一个能够拖拽的范围,把你想可以拖放的 react 代码放到 DragDropContext 中。react

支持的事件:git

  • onDragStart:拖拽开始回调github

  • onDragUpdate:拖拽中的回调npm

  • onDragEnd:拖拽结束时的回调api

    const onDragEnd = (result) => {
      console.log(result);
      /* { draggableId: "item-3", // 从这里 source:{ droppableId: "droppable", index: 2 }, // 移到这里 destination:{ droppableId: "droppable", index: 1 } } */
    };
    复制代码

WARNINGDragDropContext 不支持嵌套,且必须设置 DranDropContextonDragEnd 钩子函数(拖拽后的数组从新排序操做在这里进行)数组

Droppable

import { Droppable } from 'react-beautiful-dnd';

<Droppable droppableId="droppable-1"> {(provided, snapshot) => ( <div ref={provided.innerRef} style={{ backgroundColor: snapshot.isDraggingOver ? 'blue' : 'grey' }} {...provided.droppableProps} > <h2>I am a droppable!</h2> {provided.placeholder} </div> )} </Droppable>;
复制代码

构建一个能够被拖拽放入的区域块markdown

参数介绍:app

  • DroppableId: 此属性是必须的,用于惟一标识,不要更改此 ID。
  • directionvertical(垂直拖拽,默认)/ horizontal(水平拖拽)
  • type:指定能够被拖动的元素 class

Droppable 的 React 子元素必须是返回 ReactElement 的函数。该函数提供了两个参数:providedsnapshotide

  • provided.innerRef: 必须绑定到 ReactElement 中最高的 DOM 节点。

  • provided.droppableProps: Object,包含须要应用于 Droppable 元素的属性,包含一个数据属性,能够用它来控制一些不可见的 CSS

    console.log(provided.droppableProps);
    
    /* { data-rbd-droppable-context-id: "1", data-rbd-droppable-id: "droppable" } */
    复制代码
  • provided.placeholder: 占位符

  • snapshot: 当前拖动状态,能够用来在被拖动时改变 Droppable 的外观。

    console.log(snapshot);
    /* { draggingFromThisWith: null, draggingOverWith: null, isDraggingOver: false, 拖拽状态 isUsingPlaceholder: false } */
    复制代码

Draggalbe

import { Draggable } from 'react-beautiful-dnd';

<Draggable draggableId="draggable-1" index={0}> {(provided, snapshot) => ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} > <h4>My draggable</h4> </div> )} </Draggable>;
复制代码

可被拖拽的元素,<Draggable /> 必须始终包含在 <Droppable /> 中,能够在 <Droppable /> 内从新排序 <Draggable /> 或移动到另外一个 <Droppable />

参数介绍:

  • draggableIdstring,必需,做为惟一标识符。
  • indexnumber,匹配 DroppableDraggable 的顺序。是列表中 Draggable 的惟一索引,索引数组必须是连续的,好比 [0,1,2]
  • isDragDisabled: 默认 false,一个可选标志,用于控制是否容许 Draggable 拖动。
  • 其余同 <Droppable />

开始项目

效果预览: dnd.gif

安装

# yarn
yarn add react-beautiful-dnd

# npm
npm install react-beautiful-dnd --save
复制代码

使用 react-beautiful-dnd

import { DragDropContext } from 'react-beautiful-dnd';
复制代码

目录

└── dndPro
    ├── component
        ├── Column.js
        ├── Item.js
        ├── ItemList.js
        └── Row.js
    ├── get-initial-data.js // 初始数据
    ├── style.css // 样式文件
    └── index.js
复制代码

Dom 结构

咱们要实现三种拖拽需求:容器可拖拽元素可穿梭容器拖拽容器内部元素的垂直拖拽。咱们将经过两层不通拖拽方向的 <Droppable direction="?" /> 和虚拟列表模式实现。

第一层:容器可拖拽

<DragDropContext /> 包裹在最外层,构建一个能够拖拽的范围;添加第一个 <Droppable />,一个能够被拖拽放入的区域块,并指定拖拽方向为水平(horizontal)实现容器见的拖拽,指定拖拽类型为 column (只有 className='column' 元素可拖拽)。根据 columnOrder: ['column-0', 'column-1'] 渲染两个 Column 组件。

// index.js

import { DragDropContext, Droppable } from 'react-beautiful-dnd';

<DragDropContext onDragEnd={onDragEnd}> <div className="dnd-pro"> <Droppable droppableId="all-droppables" direction="horizontal" type="column" > {(provided) => ( <div className="columns" {...provided.droppableProps} ref={provided.innerRef} > {state.columnOrder.map((columnId, index) => ( <Column key={columnId} column={state.columns[columnId]} index={index} /> ))} {provided.placeholder} </div> )} </Droppable> </div> </DragDropContext>;
复制代码

Column 组件就是一个 <Draggable /> 元素,到这里就实现了容器可拖拽,是否是很 easy ~

// Column.js

import { Draggable } from 'react-beautiful-dnd';

<Draggable draggableId={column.id} index={index}> {(provided) => ( <div className="column" {...provided.draggableProps} ref={provided.innerRef} > <h3 className="column-title" {...provided.dragHandleProps}> {column.title} </h3> <ItemList column={column} index={index} /> </div> )} </Draggable>;
复制代码

第二层:虚拟列表

react-beautiful-dnd 支持在虚拟列表内和虚拟列表之间拖放。通常状况,建议列表的规格不超过 500 个元素。虚拟列表,是对窗口性能的一种优化,详细介绍能够看这边,咱们直接来看看用法吧:

  • 虚拟列表的行为与常规列表不一样。咱们须要告诉 rbd 咱们的列表是虚拟列表。

    <Droppable droppableId="droppable" mode="virtual">
      {/*...*/}
    </Droppable>
    复制代码
  • 拖动项目的副本

    <Droppable
      droppableId="droppable"
      mode="virtual"
      renderClone={(provided, snapshot, rubric) => (
        <div {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef} > Item id: {items[rubric.source.index].id} </div>
      )}
    >
      {/*...*/}
    </Droppable>
    复制代码

须要注意的是,placeholder 在虚拟列表中会出现问题。由于虚拟列表的长度再也不取决于元素的集体长度,而是计算视觉长度,因此咱们须要作一些处理:

const itemCount = snapshot.isUsingPlaceholder
  ? column.items.length + 1
  : column.items.length;
复制代码

对虚拟列表有初步了解后,咱们再回到 <ItemList /> 来。

新开辟一个能够被拖拽放入的区域块 <Droppable />,拖拽方向取垂直(默认),指定虚拟列表模式(mode="virtual"),发生拖拽时,使用元素副本(renderClone)。

// ItemList.js

import { FixedSizeList } from 'react-window';
import { Droppable } from 'react-beautiful-dnd';

<Droppable droppableId={column.id} mode="virtual" renderClone={(provided, snapshot, rubric) => ( <Item provided={provided} isDragging={snapshot.isDragging} item={column.items[rubric.source.index]} /> )} > {(provided, snapshot) => { const itemCount = snapshot.isUsingPlaceholder ? column.items.length + 1 : column.items.length; return ( <FixedSizeList height={500} itemCount={itemCount} itemSize={80} width={300} outerRef={provided.innerRef} itemData={column.items} className="task-list" ref={listRef} > {Row} </FixedSizeList> ); }} </Droppable>;
复制代码

<Item /> 组件和 <Row /> 组件都是一个 Draggable 元素。

onDragEnd

容器级别的拖拽和同容器内的元素拖拽,简单的交换元素便可:

// reordering list
if (result.type === 'column') {
  const columnOrder = reorderList(
    state.columnOrder,
    result.source.index,
    result.destination.index,
  );

  setState({
    ...state,
    columnOrder,
  });
  return;
}

// reordering in same list
if (result.source.droppableId === result.destination.droppableId) {
  const column = state.columns[result.source.droppableId];
  const items = reorderList(
    column.items,
    result.source.index,
    result.destination.index,
  );

  const newState = {
    ...state,
    columns: {
      ...state.columns,
      [column.id]: {
        ...column,
        items,
      },
    },
  };
  setState(newState);
  return;
}
复制代码

元素跨容器拖拽,分为两步:

  • 从源列表(source)中删除元素
  • 将元素添加到目标列表(destination)
// moving between lists
const sourceColumn = state.columns[result.source.droppableId];
const destinationColumn = state.columns[result.destination.droppableId];
const item = sourceColumn.items[result.source.index];

// 1. remove item from source column
const newSourceColumn = {
  ...sourceColumn,
  items: [...sourceColumn.items],
};
newSourceColumn.items.splice(result.source.index, 1);

// 2. insert into destination column
const newDestinationColumn = {
  ...destinationColumn,
  items: [...destinationColumn.items],
};
newDestinationColumn.items.splice(result.destination.index, 0, item);

const newState = {
  ...state,
  columns: {
    ...state.columns,
    [newSourceColumn.id]: newSourceColumn,
    [newDestinationColumn.id]: newDestinationColumn,
  },
};

setState(newState);
复制代码

React 最佳实践

相关文章
相关标签/搜索