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

因項(xiàng)目中有拖拽功能需求,于是乎在github上找到了react-beautiful-dnd這個(gè)react列表拖拽庫幫助我們實(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,用來簡(jiǎn)單的接受某一類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)起來的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)過程中根據(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)的集合大小來確定列表的尺寸结闸,而是根據(jù)itemCount來計(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)地渲染出來的dom也會(huì)足夠的多, react-virtualized便是一個(gè)react長(zhǎng)列表解決方案。

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

虛擬列表原理

虛擬列表通過判斷并只加載當(dāng)前視窗內(nè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)出第一屏后頁面閃爍的問題。

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

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

onDragEnd

該鉤子是拖拽過程中最重要的一個(gè)函數(shù)易结,也是必需的唇跨,該函數(shù)必須導(dǎo)致列表數(shù)據(jù)的重新排序。它也提供來有關(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的類型(type)买猖,droppable上設(shè)置的type值
  • result.source: draggable的起始位置(包含起始位置的index索引和droppableId)
  • result.destination: draggable完成的位置,如果用戶在超過<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代碼庫虱而,有興趣的可以看看筏餐。demo代碼庫
另附上react-beautiful-dnd官方地址。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末牡拇,一起剝皮案震驚了整個(gè)濱河市魁瞪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌惠呼,老刑警劉巖导俘,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異剔蹋,居然都是意外死亡旅薄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門泣崩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來少梁,“玉大人,你說我怎么就攤上這事矫付】Γ” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵技即,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我樟遣,道長(zhǎng)而叼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任豹悬,我火速辦了婚禮葵陵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瞻佛。我一直安慰自己脱篙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布伤柄。 她就那樣靜靜地躺著绊困,像睡著了一般。 火紅的嫁衣襯著肌膚如雪适刀。 梳的紋絲不亂的頭發(fā)上秤朗,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音笔喉,去河邊找鬼取视。 笑死硝皂,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的作谭。 我是一名探鬼主播稽物,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼折欠!你這毒婦竟也來了贝或?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤怨酝,失蹤者是張志新(化名)和其女友劉穎傀缩,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體农猬,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赡艰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了斤葱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片慷垮。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖揍堕,靈堂內(nèi)的尸體忽然破棺而出料身,到底是詐尸還是另有隱情,我是刑警寧澤衩茸,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布芹血,位于F島的核電站,受9級(jí)特大地震影響楞慈,放射性物質(zhì)發(fā)生泄漏幔烛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一囊蓝、第九天 我趴在偏房一處隱蔽的房頂上張望饿悬。 院中可真熱鬧,春花似錦聚霜、人聲如沸狡恬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弟劲。三九已至,卻和暖如春姥芥,著一層夾襖步出監(jiān)牢的瞬間函卒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留报嵌,地道東北人虱咧。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像锚国,于是被迫代替她去往敵國(guó)和親腕巡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345