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 最佳實踐

相關文章
相關標籤/搜索