[译] React 列表拖拽库 – React-Beautiful-DND教程

原文: Beautiful and Accessible Drag and Drop with react-beautiful-dnd

原文是视频课程,我只是翻译及整理的搬运工^_^。

课程介绍: 利用 React-Beautiful-DND 优雅以及简易实现拖拽效果

这堂课做一下介绍以及简单说明 React-Beautiful-DND 是什么,我们将使用它来构建项目

React Beautiful DNDReact 可以优雅以及简易实现列表拖拽效果。我的名字是 Alex,是React Beautiful DND的作者。在这课堂中,我会手把手带你去实现一个高度互动性的任务管理应用。

我将介绍 React Beautiful DND的模块,你会学到怎样使用它,在你的项目中增加强大的拖拽效果。接下来的项目支持任务重新排序,在列表间移动任务,带有条件的移动和重新排序列表。

所有效果适用于桌面和移动端,还适用于只想用键盘和屏幕阅读来体验完整效果的用户。

利用 create-react-app 创建 React环境

在这课堂中,我们将用 create react app 创建 接下来项目中的React环境

代码: codesandbox.io/embed/githu…

为了开始探索 react-beautiful-dnd,我们需要创建 React 开发环境。有一种简易的方式是利用 create-react-app,它可以通过 npm 直接使用:

npx create-react-app task-app
复制代码

运行命令行,我们在task-app文件夹中创建新的React项目。

现在安装完成,我们可以移到 task-app 文件夹,运行 yarn start (译者注: 或 npm start)来启动开发服务器。我们可以在localhost:3000看到项目。

我们有通过 create-react-app 创建的 index.js,把没有必要的文件和内容删除,现在有一个空的 React开发环境来开发项目。

用 React创建数据列表和添加样式

(译者注:建议接下来先打开代码,一边看教程,一边看代码来理解实现)

这节课我们从头开始创建和讲解任务列表。这节课创建状态模型和组件,利用它们来构建我们的任务列表。我们将基于列表实现拖拽效果的

代码: codesandbox.io/embed/githu…

我们将排序项目数据。 React-beautiful-dnd 不在意你怎样架构或管理你的数据。为了达成我们的目的,我们使用简单的数据结构。

const initialData = {
  tasks: {
    'task-1': { id: 'task-1', content: 'Take out the garbage' },
    'task-2': { id: 'task-2', content: 'Watch my favorite show' },
    'task-3': { id: 'task-3', content: 'Charge my phone' },
    'task-4': { id: 'task-4', content: 'Cook dinner' },
  },
  columns: {
    'column-1': {
      id: 'column-1',
      title: 'To do',
      taskIds: ['task-1', 'task-2', 'task-3', 'task-4'],
    },
  },
  // Facilitate reordering of the columns
  columnOrder: ['column-1'],
};

export default initialData;

复制代码

第一个数据属性是 taskstasks 对象拥有我们系统的任务。它用任务的 id 作为键,用来查询任务对象。任务对象 taskid,任务内容 content如 “take out the garbage.”。

还有 column 对象,它用来存放项目中的列。我们用 id 作为键来查询列。它是我们的 to-do 列表。

每一个列有任务id的数组,它的存在有两个目的。第一个是指定拥有权。我们知道 task-1column-1。第二是用数组来维护顺序。现在,task-1task-2 前。

我们还增加另一属性叫 columnOrder。它用来记录列的顺序。现在,我们只有一个列,日后我们可以增加更多列。

我们需要把数据导出以及返回 index.js。在index.js中,我们可以导入初始数据,它是我们渲染应用的基础。我们把初始状态 state 设定为初始数据,而应用的负责是渲染列:

columnOrder数组存放我们想要渲染的列顺序,把列从状态中取出,从而渲染我们的列。我们也要把与列相关的tasks取出。

为确保一切无误,我在项目中返回列的标题。好的,没有问题。我们可以看见渲染了唯一的列标题 to-do

我们将渲染列,而不只是标题。我们得要给列一个键,因为我们在渲染一列组件,React要用键来追踪。还有列中的任务也要的。

我们得要创建列的组件来渲染。首先,我要导入列组件…但它还不存在呢,所以我们要去创建它。对于我们的列组件,我们得要再次渲染标题。好的,不错。

我们新的列组件渲染列标题,我们还要开始在我们的项目中添加样式。为此我用了一个库叫 styled-components,它是我喜爱的组件式css in js库。

React-beautiful-dnd 对于你想用的样式方法没有任何意见。无论是预处理样式如 Less, SassCss in JS,行内样式,或其他样式机制都可以,随你选择。

我还添加了 css reset,以保持不同浏覧器的视觉一致性,你也可以用自己的css reset,或不用也可以。回到index.js,导入reset文件,你看见我们的项目看起来有点改变。

回到列中,从styled-components导入styled,我们可以用样式来创建元素。我们需要一个容器,它用来包裹元素,我们用div来实现。

我们要有标题,用h3来实现。我们要有任务列表组件,它是用来渲染我们的任务的。暂时我们用div来占位。

除了css reset,我们没有添加任何样式给列。现在要让它好看点,添加margin, borderboder-radius

我们还要添加一些padding给我们的任务列表,我把它直接加在标题和任务列表,而不是容器,它们因而与盒子边缘有些间距。

之所以要这样做,是因为之后我要拖动时突显它们,我希望颜色可以填满至边缘。你也可以用你的样式方法或间距来创建你的列表。

我们要让我们的任务列表返回一列任务,因此要渲染一列任务组件,但我们还没有创建。去导入task吧,因为还没有创建,因此先去创建它。

现在创建我们的任务组件,暂时先返回任务内容,以让我们知道传入正确的数据。好的,我们可以看见传入列的任务内容渲染出来。

让我们改善下样式吧。我们再一次创造容器以及用它把任务内容包起来。任务列表渲染出来了。

让列表更好看吧。我们给它border,一些内部padding,还要让每个项目把它之后的项目推下一点。加点border-radius,因些看起来像列了。

我们渲染我们的 to-do 列表了。

React-beautiful-dnd 来渲染列表

在课堂中,我们使用 styled-componentsinnerRef callback来为我们的styled components获取DOM ref

如果你没有在react使用过 ref,在此之前我建议你看看:

React ref documentation

egghead课程:Manipulate the DOM with React refs

ref 的小贴士

在react元素中<div>ref的回调返回 Dom 节点

如果是<Person>返回组件实例-这不是react-beautiful-dnd需要的

通常的做法是创造你自己的innerRef回调,以让你的组件返回指定的Dom节点

代码:codesandbox.io/embed/githu…

现在要用 react-beautiful-dnd 在任务列表中增加重新排序功能,先安装 react-beautiful-dnd作为依赖。好的,不错。

npm install react-beautiful-dnd
复制代码

进一步探讨之前,看一下组成 react-beautiful-dnd的组件。它是由三个不同的组件构成。第一个是DragDropContext,它是一个组件,用来包住我们需要拖拽应用部分。

droppable创建区域可以放入,它包含draggable组件,draggable组件是可以在droppable拖拽的组件。为了使列中的内容可以拖拽,我们用DragDropContext
裹它。

首先,我们从 react-beautiful-dnd 导入 DragDropContextDragDropContext组件有三个回调。onDragStart在拖拽发生时调用。onDragUpdate在拖拽发生变化时调用,例如某项目移到新位置。 onDragEnd 在拖拽结束时调用。

DragDropContext 只有 onDragEnd是必须传入。这是你的onDragEnd函数的负责去同步更新你的状态,反映拖拽的结果。我们先留空函数,以后再实现。

我们要增强列组件的功能了。从 react-beautiful-dnd 导入 droppable 组件,把我们的任务列表放入 droppabledroppable只要求一个prop: droppableId

这id要求在 DragDropContext中唯一的。我们要用column的id。你可以看见应用报错:”Children is not a function.”。droppable使用 render props设计模式,它期望包裹的子组件是函数,返回组件。

使用render props的一个原因是 react-beautiful-dnd不想创造任何DOM节点。你自己创造自己想要的组件。react-beautiful-dnd融入你已有的结构。

函数第一个参数是provided,它是一个对象,有几个重要的存在意义。它有一个为droppableProps而设的属性。这些props需要应用至你想要作为拖拽区域的组件。

在文档中,我们明确列出所有这些props。你可以单独使用它们,甚至去增上补丁,完善它们。然而,我们不会走向这话题。

一般来说,你可以直接在你的组件展开 provided.droppablePropsprovided对象中有一个属性叫innerRef,它是一个函数,用来把你组件中的DOM节点给react-beautiful-dnd

styled-component有一个回调propinnerRef,它返回组件的DOM节点。我们可以把provided.innerRef传给它。最后要做的是在droppable插入占位,provided.placeholder

占位是一个 React元素,用来在拖拽时,以便需要增加 droppable拖拽区域的空间,占位要作为droppable的子组件。我们的droppable做好了。

我们去到任务组件,使它可拖拽。从 react-beautiful-dnd 导入draggable组件。现在我用 draggable组件来包裹容器组件。

draggable组件有两个必要的props。第一是 draggable-id,这里会用任务的id来赋值。第二是index。我们现在没有传索引给任务组件。回到列组件来实现吧。

列组件把prop里的任务用map组成任务组件。map函数的第二个参数是数组的索引。我们把索引传给任务组件。如果回到任务组件,我们依然有问题。

正如droppabledraggable期望它的子组件是函数。第一个函数参数是provided对象,它就像之前在droppableprovided对象。

provided对象有一个属性叫 draggableProps,它们要用在需要拖拽的组件,当用户输入时,组件可以作出回应。provided还有另一个属性叫 dragHandleProps。这些props要用在控制拖拽部分,因而使这部分可以控制整个组件。

你可以利用这点,只有组件的一小部分来拖动大的组件。在我们的应用里,我们想要整个任务可以拖动,所以把props应用至同一元素。正如droppable,我们要提供refdraggable

现在看下我们的应用。我们可以不用滑鼠和键盘来拖动项目。这真不错。当我们拖动项目,我们可以看见它背面的内容。这看起来不太好。

回到我们的任务组件。增加背景色。白色就可以了。现在移动项目时,我们不会看到背后的内容。好啦,我们有看起来不错的重新排序的经验。

你可能注意当我拖动时,排序没有保留下来。为了实现持久化,我们要在onDragEnd回调函数中实现我们的排序逻辑。

利用 onDragEnd 回调持久化列表排序

这节课展示怎样把拖拽互动的结果持久化

要实现效果,你将实现 onDragEnd 回调函数,它会在每次拖拽结束时调用。函数接收一个result对象。我们会使用sourcedestination属性来更收任务列表的顺序

代码:codesandbox.io/embed/githu…

现在我们可以拖拽任务列表的内容,但互动的结果没有持久化,所以在拖放完成后,任务列表又返回原本的状态。我们得要在onDragEnd函数完成持久化。

首先,什么是result对象?我们被给予什么信息?这里就有例子,result对象。resultdraggableId,它是用户拖动的元素id。它有类型属性,它会之后再探讨,还有拖放原因,它会是放下或取消。

一般来说,明确知道用户是拖放或用取消键取消不是非常重要,但你如有需要,可以获取信息。我们现在用两个真的重要的对象,sourcedestination对象。

这些对象含有位置信息,拖动的开始和结束位置。在这项目中,draggable组件开始于column-1,索引为0,结束于column-1和索引为1。destination可能为null,例如用户拖出列表。

你可以从result对象获取我们感兴趣的信息。如果没有destination,没有什么需要做,我们可以直接退出。

我希望增加一些小检查,去校验拖动位置的变更。通过检查sourcedestinationdraggableId,还有它们的索引是否是一致,可以实现效果。如果两个校验都为真,表示用户把拖动项目放回原来位置,什么都不用做了。

我们需要重新排序列的taskIds数组,我们先获取状态中的列。我已经从source中获取droppable的id来查询状态中的列。

我本可以直接用column-1来获取列,因为我知道只有一列,但用source.droppableId会使程序更健壮。如果用column-1的话,我日后改初始数据,idcolumn-1也要改了。

当我更新时,我一般避免改变原有的状态,而是複製一个新对象,然后改变它。

我们需要把taskIds数组里的旧索引移到新索引。splice可以做什么?它会把指定索引的元素移除。

我们得要用多一次splice。这次要利用destination的索引,但我们什么都不删除,而是插入draggableId,即task的id。排序数组有不同的方法,这只是其中之一。

我要创造新列。它与旧列有相同属性,只是有新的数组。我们要把它放入状态。

我使用对象展开,一方面维护原来的状态,另一方面合并掉需要改变的属性。

我要插入新列,它会覆盖原有的列。技术上来说,我们不需要展开,因为我们只有一列,但我觉得这样做不错。调用this.setState,它会更新react组件。回到任务列表,我们可以说排序持久化完成。

我们已经乐观更新UI,没有等待几个确认。你怎样保存变化取决你的状态解决方案和服务器架构。

改变持久化的简单策略会是乐观更新后,呼叫终端,以让你的服务器知道重新排序。

(译者注:乐观更新即是先改变视图,然后才向后端发出改变请求)

拖动时用 react-beautiful-dndsnapshot Values自定义应用的外观

<Draggable /><Droppable />组件提拱它们子函数的快照对象。当拖动发生时,快照对象可以用来创造美丽的视觉队列

代码:codesandbox.io/embed/githu…

任务列表重新排序看起来真不错。如果我们想当拖动时,添加样式?react-beautiful-dnd提拱大量信息关于拖动互动的状态,它可以帮助你添加视觉效果。

你可以更新任何事物,当你拖动时,这不会改变你的draggabledroppable组件维度。改变组件的背景色是一个不错的方式去指明什么在移动和什么最近被拖动。

我们要怎样做呢?回到任务组件,组件函数的第二个参数是snapshot,它包含一系列属性,让你可以在拖动时,添加样式。

我们有 draggablesnapshot对象。snapshot里有两个属性。第一个是布尔flagisDragging,当draggable被拖动时,它的值为真。另一个是draggingOver,当draggable被拖入droppable区域时,它是一个droppable的id。如果draggable在拖动,没有进入droppable区域时,draggingOvernull

当任务拖动时,我们将用snapshot来改变任务的背景色。我现在所做的是把isDragging作为句prop,传入给我的样式组件,它有snapshot.isDragging属性的值。

利用样式组件的api,我可以根据prop来设定组件的背景色。如果isDragging为真,我想把背景色设为亮绿,否则为白色。

现在当我们拖动任务,拖动物件的背景色变为亮绿。我们也可以在droppable实现同样效果,droppable函数的第二个参数也是snapshot

droppablesnapshot有两个属性。首先,它是一个布尔flag,叫isDraggingOver,当draggable拖入droppable的区域时为真。另一属性是draggingOverWith,它是进入droppabledraggable的id。如果没有任何draggabledroppable区域,它的值是null。

当发生拖动时,我们要去改变任务列表的背景色。我要把isDraggingOver作为prop给任务样式组件。我要让它配上snapshot.isDraggingOver的布尔值。

如果任务列表的isDraggingOver为真,我要把任务列表的背景色变为天蓝色。否则为白色。当我提起一个任务,任务列表的背景色为天真,当我移出列表,背景色变回白色。

我喜欢为我的droppables加点过渡效果,当你在droppable间拖拽时,它的颜色转变看起来更吸引人。

dragHandleProps来设计拖动控制

react-beautiful-dnd中,拖放元素(draggable) 与怎样拖放 (drag handle) 是分开的。这节课会解释它们的区别,以及演示怎样处理draggable的控制

<Draggable/>的子函数接收provided对象,它有一个属性叫dragHandleProps。这属性让我们可以指定<Draagable/>哪一部分可以控制拖动。你将看见怎样把属性传入给组件来设计控制。

代码:codesandbox.io/embed/githu…

拖动处理是draggable的一部分,可以用来控制整个draggable的拖动。对于我们的任务组件,draggable和拖动处理都是同一组件。这意味着,我们可以从任务组件任何部分来拖动组件。

然而这不是一定的。现在创造一个新的组件,用来控制任务组件。现在我们遇到错误,因为我们还没创造组件。

我用div元素来创造拖动的处理,但你可以用任何元素来处理。我给它20像素宽,20像素高,背景色是橙色,4像素的border radius。我们希望拖动处理元素位于任务组件左面。

我将把我们的容器变成flex布局。即使我们已经创造了拖动处理元素,我们依然可以在任意部分拖动,这是因为我们依然把dragHandleProps传给容器。

要想使拖动处理组件生效,我们要做的是,把dragHandleProps在组件中展开。现在我们不再可以从内容中拖动draggable了。我们只能用拖动组件(Handle)来拖动,键盘控制拖动也是同理。

自定义拖动控制是非常有用的。然而,在这项目中,我不觉得它是有必要的。我认为可以在任务组件中随意拖动的体验是不错的。在下一节课,我不会保留这次的变动。

利用 onDragEnd 把项目在列之间移动

在这节课中,我们放置多列任务列表,任务可以在不同列移动。

现在我们的应用有多列,需要重构 onDragEnd处理多列情况。我们将增加新的逻辑,在状态中把任务移到其他列。

代码:codesandbox.io/embed/githu…

在这节课中,我们要增加多列,使任务可以在列间移动。首先,回到初始数据。

现在只有一列。我增加多两列,一个是进度列(in progress),另一个是完成列(done)。我也需要增加这些列的id给列排序数组。

我们的应用现在渲染三列。我们需要更新样式,使列并排。回到index.js,增加组件叫Container,它会包裹我们的列。

[00:58] This container will be a flex parent that will align the items next to each other. I have an important style components here. I’ll just do that. Our columns are being printed next to each other. However, that to do column which has tasks in it is much wider than the other ones.

它是一个flex父元素,使子元素并排。然而,to do列比其他要宽。

回到列组件。这里没有容器组件。我给这些列增加寛度。现在,所有列有相同寛度。缩放一点,我们可以看下发生什么。

当我们用鼠标徘徊在其他列,它们没有高亮。这是因为进度列和完成列没有任务,现在没有高度。要解决这问题,我们要使容器为flex父元素,flex-directioncolumn

子元素现在垂直排列。对于我们的任务列表,我想要flex元素变大。现在flex盒子填满可用空间。如果我徘徊这些列,我会获得正确的颜色。

值得一提,我想给droppable组件加上最小高度,因而使如果所有列为空时,依然可以把任务放入列中,不然的话,要把任务放入列中不是一件易事。

回到应用,如果我们把任务放入进度列,任务又回到原来位置。我们需要更新onDragEnd函数的重新排序逻辑,使它可以处理列间移动的情况。

index.js中,之前我们用source.droppableId属性去查询列。在一列的情况下,拖拽是没有问题的。然而,不足以处理多列情况。

我们用两个变量名来存下起始与终结列:start和end。如果它们指向同一列,就执行之前的逻辑。

如果我重新排序同一列,结果依然保存下来。现在要处理不同列的情况,我们要用与之前类似的方法。

我创造了一个新的起始task-id数组,它与旧的一致,我要从数组移除拖放的任务id。另一方面,我也创造了一个新的起始列,也是与旧的一致,但没有新的起始task-id数组。

我也要为终结的task-id数组複製一个新的数组,它与旧的相同。我要用splice来在指定的索引插入draggableId。我又创造一个新的列,它用来存放新的终结列,终结列有新的task-id数组。

最后,我们要创造一个新的状态对象,它有与旧的相同属性,但列与任务的数据已经更新。

处理过程与之前渲染单一列排序是类似的。不同之处在于我们要用不同组件的行为来处理不同列,而不是同一列。

现在我们的应用可以把任务移到任何列,这也可以用键盘来操作。

移动条件限制

现在我们可以在任务列表间移动任务,我们可能不会想用户可以移动到随意位置。这节课会探索如果条件限制拖放。

react-beautiful-dnd提供健壮的api,配置什么可以拖拽,哪裡可以拖放。isDragDisabled可以设于<Draggable /><Droppable />,而<Droppable />的类型prop (type) 给予你细额粒度的控制,允许你实现任何业务逻辑,控制什么可以拖或放。

(译者注:这一节的示例有bug,如果把任务在不同列随意移动,会报错,我也搞不懂原因。但如果一开始不允许某组件移动是没有问题)

代码:codesandbox.io/embed/githu…

利用 react-beautiful-dnd,只要利用prop isDragDisabled,你可以去控制什么可以拖,哪裡可以放

isDragDisabled为真,它会阻止Draggable被拖动。我要做的是使id为task-1的组件不可移动。我们可以看见第一个任务组件不可移动了,但其他还是可以的。

我要把它拉出,放入变量,使它可以应用至样式组件。我要用三元表达式来设定背景色,如果Draggable组件是不可移动,给它浅灰色。

我们现在可以比较容易看到什么不可移动。isDragDisabled阻止Draggable拖动,但它依然可以重新排序。

你要怎样用 isDragDisabled,随你所愿。它可以基于一些複杂的业务逻辑。我们走到列组件,有两个可用机制控手月中弓Draggable哪裡可以放。

最简单的机制是用Droppabletype prop,你可以提供可选类型给Droppable。一个Draggable只能拖入同一类型的Droppable

我根据列的id,选择性给Droppable类型。第三列有Done类型,但首两列有Active类型。

这设置允许我在To-DoIn-Progress列间移动,但不能移到Done列。第二个机制可以控制在哪可以拖放是DroppableisDropDisabled

如果isDropDisabled设为真,即使是同一类型,任何Draggable不能拖入。你可以动态修改isDropDisabled,甚至在拖动期间。

我在Droppable中设定isDropDisabled,用这个prop,我们可以使任务只能向右移动。

我创造了一个 onDragStart函数捕获开始拖动时列的索引。当拖动完成时,我要确保我清除索引。

我要把 onDragStart函数绑在DragDropContext。我从数组的map函数中获得列的索引。我要创造一个变量叫isDropDisabled

当从map获取的索引少于开始的列的索引,IsDropDisabled设为真。这会阻止向后拖动。

我要把它传给列了。我可以把任务从To-Do列移至In-Progress列,但不能移回To-Do列,只能移至Done列。

利用 prop 的 direction 进行水平排序

在这节课中,我们会演示用怎样创造一个水平排序的 Droppable

The component provides us with a direction prop that we can use to set a list of data to be sortable in the vertical or horizontal plane.

代码:codesandbox.io/embed/githu…

Instructor: [00:00] Up to now, we’ve only looked up vertical reordering in our application. React-beautiful-dnd also supports reordering draggables on the horizontal plane.

现在,我们只看见垂直排序。react-beautiful-dnd也支持水平排序。

首先回到初始数据。这节课只用一列。我把任务列表变成flex父元素。

我们可以看见项目从左至右。我们移除容器的宽度,因为我们不再需要,还有flex-growing,我指高度属性。

在任务组件,我不再把所有内容渲染,只渲染第一个字符。我给我们的容器40像素宽度,40像素高度,还有把border-radius改成50%,变成圆形。

我不想在任务列表底部保留空白,而是放在右边。我用flex盒子把内容水平和垂直居中。我们的任务列表做好了,现在按水平排列。

当我开始拖动,感到一些异状,不是我们所期待的。我们需要回到列组件,改变droppable的prop,它有一个可选的prop叫direction

默认情况下,droppable是垂直排序。通过改变direction的prop为 horizontal,你可以有一个水平排序的列表。如果我们的例子,我们可以说,我们可以水平重新排序。

键盘操作也是有效的。我们聚焦某个draggable,我们会得默认的聚焦轮廓。这看起来挺有趣,因为我们的任务排序变得通用。

给聚焦状态加点样式,取消默认轮廓,把边框的颜色变成红色,把边框加寛一点。

重新排序

在这节课,我们把之前学到的技术应用在列的重新排序中。

要实现这目标,你要用<Droppable/>来包裹列,水平排序,使列可拖动。你可以看到这与之前任务排序类似的。我们将用嵌套 <Draggable /><Droppable /> 组件,还有Droppable的类型prop来实现列排序。

代码:codesandbox.io/embed/githu…

这节课,我们添加列排序功能。我已经做了方法演示。现在我们有几个droppable组件垂直排序。

这些是我们的任务列表,标黄出现在这里。它们拥有任务,draggable组件。我们添加一个 droppable父元素,它使子元素水平排序,然后把列组件变成 draggable组件。

这方法包含嵌套使用 draggabledroppable。我们把列水平排序,任务垂直排序。

稍微改变初始数据,把第三列移除,因而比较容易看到效果。从 react-beautiful-dnd 导入droppable,创造水平排序的droppable

我用一个droppable包裹我们的容器组件。它的id是什么不重要,因为它不会与其他的droppable有互动。称呼它为所有列(all-columns)吧。

我们需要把droppable的方向设定为水平,使我们的列是水平排序。另外,我们也要用列的值增加类型prop。给all-columnsdroppable不同的类型,使它不会与任务列表的droppable有冲突。

设定其馀的droppable。我把droppable的props给这组件,还有DOM的引用。我还为droppable增加占位。现在处理列组件,使它可拖动。首先从react-beautiful-dnd导入draggable

把整个列包裹在draggable中,用列的id作为draggable的id。draggable也需要索引,它表示在droppable的位置。我们现在没有提拱索引,所以需要提供。

map函数的第二个参数是数组的索引。我们可以直接传给列组件。

我把draggable的props应用给我们容器,还有处理innerRef函数,但我还没有应用dragHandleProps,这导致错误发生。

我们用列的标题作为拖动控制,给包裹任务的容器增加类型。

看一下我们的应用。我们依然可以重新排序任务。当把列排序时,样式看起来挺有趣,但当放下列时,就报错了。有些事我们需要去做。

我们遇到错误,是因为我们还要更新onDragEnd函数的排序逻辑。我们从 result对象中取出type属性,通过它去知道拖动的是列还是任务。

我们需要增加一些逻辑给列,以便处理它的排序。我创造新的列排序数组,它与旧的有相同的值。我要从原来的索引移除旧的列的id,插入到新的位置。

我要创造一个新的状态对象,它与旧的一样,但有新的列排序数组。回到我们的应用。列的重新排序结果是保存下来。然而,我拖动时,依然可以看到列头部背后的内容。

我没有只为标题加上背景色,我为整个列加上背景色。我要改变任务列务,加上条件判断,设定背景色,它的背景色不是设为白色,而是继承容器的白色。

现在我拖动列,你没有看到它背后的内容。我们现在可以重新排序任务,移动任务到其他列,排序列。我们也可以用键盘来操作。

利用 shouldComponentUpdatePureComponent 来优化性能

我们可以不用重新渲染 DraggableDroppable 组件更新样式,通过构建 <InnerList/> 组件,它会利用React的shouldComponentUpdate生命週期鈎子函数来阻止渲染。

之后你会知道怎样用React的PureComponent来实现同样的优化。

代码:codesandbox.io/embed/githu…

在这节课中,我要谈下一些重要的性能优化,你可以利用它们使你的应用运行更快。

React的一个性能瓶颈是reconciliation过程。简单来说,一些组件没有任何变化,你不想去重新渲染它们。我要去用React在Chrome的开发者工具插件,它可以在Chrome的网上商店下载。

这插件有一个超好用的功能,叫 highlight updates。通过这功能,开发者工具会高亮渲染的组件。

当列拖动时,我要改变背景色,从天蓝色变成浅灰色,因而使我们可以看见到底发生什么。

当我拿起任务,你可以看见所有任务高亮成蓝色。这显示组件的渲染函数被调用。当droppable里的列调用它的子函数,它要渲染它所有的任务。

snapshot函数的值发生变更时,droppable函数被调用。无论你进入或离开,这都会发生。正如你所见,当我进入或离开,所有任务变蓝,意味着渲染方法被调用。

我们希望snapshot的值发生变化时,我们的任务列表作出响应,正如我们想拖动时,背景色发生变化。然而,我们不想为了使样式改变,而使所有任务都渲染。我要用新的组件去替代。

我们把映射返回任务组件的逻辑旅入InnerList组件。我要增加 shouldComponentUpdate生命週期方法。

如果新的任务数组与原来的数组引用相等,我们跳过渲染。如果引用改变,我们允许渲染。现我们拖动任务,我们可以更新背景色,但不用进行没有必要的渲染。

当我拖动列,所有在列中的任务都渲染。你可以看见所有任务都变蓝,意味着渲染发生。

这里是列的droppable,当我拖动列,所有任务渲染,因为snapshot的值发生变更,即使不调用droppable函数,也依然被调用。

droppable函数被调用,它会返回新的列表组件。正如列组件,我们想避免droppable的子组件渲染,在这例子中,是拖动时的容器组件。

我们不再是返回列组件,而是返回新的InnerList组件,它负责渲染列组件。关键的不同之处在于不是动态创造新的任务数组,而是任务对象的映射。

InnerList组件与之前在droppable函数所做的一样,如果只是原封不动,不会有任何性能优化。

如果没有任何prop改变,我们可以作条件判断,阻止渲染。优化的关键之处在于不是动态生成任务数组,而是传入整个任务的映射。这三个属性因此不会在拖动时发生变化。

当拖动时增加样式给列,不会发生渲染。我们可以使组件继承PureComponent,它与shouldComponentUpdate的校验是一样的。

现在当拖动列表时,我们看见任务不再为高亮为蓝色,意味着不再被重新渲染。

(译者注:剩下还有一节的,关于与screen reader的互动,但我觉得内容不太重要,就不翻译了,感兴趣可以自己去看看)

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享