四时宝库

程序员的知识宝库

简化拖放(列表和嵌套列表)(列表拖拽)

我总是忘记如何使用任何拖放库,直到我不得不再次使用它。目前,我一直在合作的是 react-beautiful-dnd,这是 Atlassian 为 Jira 等产品创建的库。我承认,我通常不是 Atlassian 产品的最大粉丝,但它是一个很好的拖放库,特别是对于列表的使用。

react-beautiful-dnd 确实有点冗长,尤其是在使用嵌套列表时,所以我将很多细节移到了可重用的组件上,并制作了一些演示与您分享。

目标及样例

我为这篇文章做了两个演示。首先,我创建了拖放组件,以简化 react-beautiful-dnd 库的使用。在第二个中,我再次使用这些组件进行嵌套拖放,您可以在其中拖动类别或在类别之间拖动项目。

  • 拖放演示
  • 嵌套拖放演示

这些演示几乎没有任何样式 - 我更感兴趣的是以尽可能少的样式展示原始功能,所以不要太关注它有多漂亮(不是)。

简单的拖放列表

首先,我们将制作一个简单的拖放列表。

您需要一个重新排序功能来获取拖放内容的新顺序:

helpers.js

export const reorder = (list, startIndex, endIndex) => {
  const result = Array.from(list)
  const [removed] = result.splice(startIndex, 1)
  result.splice(endIndex, 0, removed)

  return result
}

DragDropContext、Draggable 和 Droppable 组件用于创建包含可拖动项的列表,因此我创建了一个 ListComponent 来处理完整的可拖动/可放置列表:

ListComponent.js

import React, { useState } from 'react'
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd'
import { reorder } from './helpers.js'

export const ListComponent = () => {
  const [items, setItems] = useState([
    { id: 'abc', name: 'First' },
    { id: 'def', name: 'Second' },
  ])

  const handleDragEnd = (result) => {
    const { source, destination } = result

    if (!destination) {
      return
    }

    const reorderedItems = reorder(items, source.index, destination.index)

    setItems(reorderedItems)
  }

  return (
    <DragDropContext onDragEnd={handleDragEnd}>
      <Droppable droppableId="droppable-id">
        {(provided, snapshot) => {
          return (
            <div ref={provided.innerRef} {...provided.droppableProps}>
              {items.map((item, index) => {
                return (
                  <Draggable draggableId={item.id} index={index}>
                    {(provided, snapshot) => {
                      return (
                        <div ref={provided.innerRef} {...provided.draggableProps}>
                          <div {...provided.dragHandleProps}>Drag handle</div>
                          <div>{item.name}</div>
                        </div>
                      )
                    }}
                  </Draggable>
                )
              })}
              {provided.placeholder}
            </div>
          )
        }}
      </Droppable>
    </DragDropContext>
  )
}

如您所见,即使在最简单的示例中,它也嵌套了 12 层深。好在我们在 JavaScript 中使用了 2 空格缩进!我们还有多个提供和快照要处理,当它嵌套时,您现在有四个和多个占位符,所以它开始变得非常混乱。

我不使用隐式返回使情况变得更糟,但就我个人而言,我真的很不喜欢使用隐式返回,因为它使调试(阅读:console.log())东西变得更加困难。

无论如何,我喜欢将它们分解成自己的组件:拖放。

Drop

Drop 包含 Droppable,并传递引用和道具。我还添加了一个类型,该类型将与嵌套拖放一起使用,但暂时可以忽略。

Drop.js

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

export const Drop = ({ id, type, ...props }) => {
  return (
    <Droppable droppableId={id} type={type}>
      {(provided) => {
        return (
          <div ref={provided.innerRef} {...provided.droppableProps} {...props}>
            {props.children}
            {provided.placeholder}
          </div>
        )
      }}
    </Droppable>
  )
}

Drag

拖动手柄拖动手柄(奇数句),通常由六个点的图标表示,但对于此简化示例,它只是一个带有一些文本的 div。

Drag.js

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

export const Drag = ({ id, index, ...props }) => {
  return (
    <Draggable draggableId={id} index={index}>
      {(provided, snapshot) => {
        return (
          <div ref={provided.innerRef} {...provided.draggableProps} {...props}>
            <div {...provided.dragHandleProps}>Drag handle</div>
            {props.children}
          </div>
        )
      }}
    </Draggable>
  )
}

合在一起

我发现制作一个导出所有内容的索引.js组件很有用。

index.js

import { DragDropContext as DragAndDrop } from 'react-beautiful-dnd'
import { Drag } from './Drag'
import { Drop } from './Drop'

export { DragAndDrop, Drag, Drop }

Usage使用

现在,您可以使用拖放来代替本文开头的所有废话:

import React, { useState } from 'react'
import { DragAndDrop, Drag, Drop } from './drag-and-drop'
import { reorder } from './helpers.js'

export const ListComponent = () => {
  const [items, setItems] = useState([
    /* ... */
  ])

  const handleDragEnd = (result) => {
    // ...
  }

  return (
    <DragAndDrop onDragEnd={handleDragEnd}>
      <Drop id="droppable-id">
        {items.map((item, index) => {
          return (
            <Drag key={item.id} id={item.id} index={index}>
              <div>{item.name}</div>
            </Drag>
          )
        })}
      </Drop>
    </DragAndDrop>
  )
}

您可以在演示中看到整个事情协同工作。

更易于阅读,只要使用相同的拖动手柄样式,您就可以根据需要制作可拖动的外观。(我只添加了最基本的样式来区分元素。

带有类别的嵌套拖放列表

这些组件还可用于嵌套拖放。最重要的是为嵌套拖放添加一个类型,以区分是在同一外部类别内拖放,还是在类别之间拖放。

首先,我们将拥有一个类别数组,而不是只有一个项目数组,该数组中的每个对象都将包含项目。

const categories = [
  {
    id: 'q101',
    name: 'Category 1',
    items: [
      { id: 'abc', name: 'First' },
      { id: 'def', name: 'Second' },
    ],
  },
  {
    id: 'wkqx',
    name: 'Category 2',
    items: [
      { id: 'ghi', name: 'Third' },
      { id: 'jkl', name: 'Fourth' },
    ],
  },
]

handleDragEnd 函数变得更加复杂,因为现在我们需要处理三件事:

  • 拖放类别
  • 拖放同一类别中的项目
  • 将项目拖放到其他类别中

为此,我们将收集源和目标的可放置 ID,这将是类别 ID。然后,要么是简单的重新排序,要么需要将源添加到新目标。在下面的新handleDragEnd函数中,您可以看到处理的所有三种情况:

const handleDragEnd = (result) => {
  const { type, source, destination } = result

  if (!destination) return

  const sourceCategoryId = source.droppableId
  const destinationCategoryId = destination.droppableId

  // Reordering items
  if (type === 'droppable-item') {
    // If reordering within the same category
    if (sourceCategoryId === destinationCategoryId) {
      const updatedOrder = reorder(
        categories.find((category) => category.id === sourceCategoryId).items,
        source.index,
        destination.index
      )
      const updatedCategories = categories.map((category) =>
        category.id !== sourceCategoryId ? category : { ...category, items: updatedOrder }
      )

      setCategories(updatedCategories)
    } else {
      // Dragging to a different category
      const sourceOrder = categories.find((category) => category.id === sourceCategoryId).items
      const destinationOrder = categories.find(
        (category) => category.id === destinationCategoryId
      ).items

      const [removed] = sourceOrder.splice(source.index, 1)
      destinationOrder.splice(destination.index, 0, removed)

      destinationOrder[removed] = sourceOrder[removed]
      delete sourceOrder[removed]

      const updatedCategories = categories.map((category) =>
        category.id === sourceCategoryId
          ? { ...category, items: sourceOrder }
          : category.id === destinationCategoryId
          ? { ...category, items: destinationOrder }
          : category
      )

      setCategories(updatedCategories)
    }
  }

  // Reordering categories
  if (type === 'droppable-category') {
    const updatedCategories = reorder(categories, source.index, destination.index)

    setCategories(updatedCategories)
  }
}

现在,您可以看到该类别具有可放置类别类型,并且该项目具有可放置项目类型,从而区分了它们。我们现在有两层 <Drop> 和 <Drag> 组件。

NestedListComponent.js

import React, { useState } from 'react'
import { DragAndDrop, Drag, Drop } from './drag-and-drop'
import { reorder } from './helpers.js'

export const NestedListComponent = () => {
  const [categories, setCategories] = useState([
    /* ... */
  ])

  const handleDragEnd = (result) => {
    /* ... */
  }

  return (
    <DragAndDrop onDragEnd={handleDragEnd}>
      <Drop id="droppable" type="droppable-category">
        {categories.map((category, categoryIndex) => {
          return (
            <Drag key={category.id} id={category.id} index={categoryIndex}>
              <div>
                <h2>{category.name}</h2>

                <Drop key={category.id} id={category.id} type="droppable-item">
                  {category.items.map((item, index) => {
                    return (
                      <Drag key={item.id} id={item.id} index={index}>
                        <div>{item.name}</div>
                      </Drag>
                    )
                  })}
                </Drop>
              </div>
            </Drag>
          )
        })}
      </Drop>
    </DragAndDrop>
  )
}

如果没有拖放组件,我什至不会向您展示这是什么样子。

在嵌套拖放演示中,您可以测试在类别之间拖动、在类别内拖动以及拖动类别本身(包括其包含的所有项目)。

总结

拖放可能会变得非常笨拙,尤其是在将 react-beautiful-dnd 库用于嵌套列表时。通过创建可重用的组件,您可以使其更易于使用和理解。

我总是试图看看我的工作在似乎失控时是否可以驯服它,这只是一个例子。希望您喜欢这篇文章和演示!

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接