原文: Beautiful and Accessible Drag and Drop with react-beautiful-dnd
原文是视频课程,我只是翻译及整理的搬运工^_^。
课程介绍: 利用 React-Beautiful-DND 优雅以及简易实现拖拽效果
这堂课做一下介绍以及简单说明 React-Beautiful-DND 是什么,我们将使用它来构建项目
React Beautiful DND
让 React
可以优雅以及简易实现列表拖拽效果。我的名字是 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;
复制代码
第一个数据属性是 tasks
。tasks
对象拥有我们系统的任务。它用任务的 id
作为键,用来查询任务对象。任务对象 task
有 id
,任务内容 content
如 “take out the garbage.”。
还有 column
对象,它用来存放项目中的列。我们用 id
作为键来查询列。它是我们的 to-do
列表。
每一个列有任务id的数组,它的存在有两个目的。第一个是指定拥有权。我们知道 task-1
在 column-1
。第二是用数组来维护顺序。现在,task-1
在 task-2
前。
我们还增加另一属性叫 columnOrder
。它用来记录列的顺序。现在,我们只有一个列,日后我们可以增加更多列。
我们需要把数据导出以及返回 index.js
。在index.js
中,我们可以导入初始数据,它是我们渲染应用的基础。我们把初始状态 state
设定为初始数据,而应用的负责是渲染列:
columnOrder
数组存放我们想要渲染的列顺序,把列从状态中取出,从而渲染我们的列。我们也要把与列相关的tasks
取出。
为确保一切无误,我在项目中返回列的标题。好的,没有问题。我们可以看见渲染了唯一的列标题 to-do。
我们将渲染列,而不只是标题。我们得要给列一个键,因为我们在渲染一列组件,React
要用键来追踪。还有列中的任务也要的。
我们得要创建列的组件来渲染。首先,我要导入列组件…但它还不存在呢,所以我们要去创建它。对于我们的列组件,我们得要再次渲染标题。好的,不错。
我们新的列组件渲染列标题,我们还要开始在我们的项目中添加样式。为此我用了一个库叫 styled-components
,它是我喜爱的组件式css in js库。
React-beautiful-dnd
对于你想用的样式方法没有任何意见。无论是预处理样式如 Less
, Sass
或 Css in JS
,行内样式,或其他样式机制都可以,随你选择。
我还添加了 css reset
,以保持不同浏覧器的视觉一致性,你也可以用自己的css reset
,或不用也可以。回到index.js
,导入reset
文件,你看见我们的项目看起来有点改变。
回到列中,从styled-components
导入styled
,我们可以用样式来创建元素。我们需要一个容器,它用来包裹元素,我们用div
来实现。
我们要有标题,用h3
来实现。我们要有任务列表组件,它是用来渲染我们的任务的。暂时我们用div
来占位。
除了css reset
,我们没有添加任何样式给列。现在要让它好看点,添加margin
, border
和boder-radius
。
我们还要添加一些padding
给我们的任务列表,我把它直接加在标题和任务列表,而不是容器,它们因而与盒子边缘有些间距。
之所以要这样做,是因为之后我要拖动时突显它们,我希望颜色可以填满至边缘。你也可以用你的样式方法或间距来创建你的列表。
我们要让我们的任务列表返回一列任务,因此要渲染一列任务组件,但我们还没有创建。去导入task
吧,因为还没有创建,因此先去创建它。
现在创建我们的任务组件,暂时先返回任务内容,以让我们知道传入正确的数据。好的,我们可以看见传入列的任务内容渲染出来。
让我们改善下样式吧。我们再一次创造容器以及用它把任务内容包起来。任务列表渲染出来了。
让列表更好看吧。我们给它border
,一些内部padding
,还要让每个项目把它之后的项目推下一点。加点border-radius
,因些看起来像列了。
我们渲染我们的 to-do 列表了。
用 React-beautiful-dnd
来渲染列表
在课堂中,我们使用 styled-components
的 innerRef callback来为我们的styled components
获取DOM ref
。
如果你没有在react
使用过 ref
,在此之前我建议你看看:
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
导入 DragDropContext
。DragDropContext
组件有三个回调。onDragStart
在拖拽发生时调用。onDragUpdate
在拖拽发生变化时调用,例如某项目移到新位置。 onDragEnd
在拖拽结束时调用。
DragDropContext
只有 onDragEnd
是必须传入。这是你的onDragEnd
函数的负责去同步更新你的状态,反映拖拽的结果。我们先留空函数,以后再实现。
我们要增强列组件的功能了。从 react-beautiful-dnd
导入 droppable
组件,把我们的任务列表放入 droppable
, droppable
只要求一个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.droppableProps
。provided
对象中有一个属性叫innerRef
,它是一个函数,用来把你组件中的DOM节点给react-beautiful-dnd
。
styled-component
有一个回调prop
叫 innerRef
,它返回组件的DOM节点。我们可以把provided.innerRef
传给它。最后要做的是在droppable
插入占位,provided.placeholder
。
占位是一个 React元素,用来在拖拽时,以便需要增加 droppable
拖拽区域的空间,占位要作为droppable的子组件。我们的droppable
做好了。
我们去到任务组件,使它可拖拽。从 react-beautiful-dnd
导入draggable
组件。现在我用 draggable
组件来包裹容器组件。
draggable
组件有两个必要的props
。第一是 draggable-id
,这里会用任务的id来赋值。第二是index
。我们现在没有传索引给任务组件。回到列组件来实现吧。
列组件把prop里的任务用map组成任务组件。map
函数的第二个参数是数组的索引。我们把索引传给任务组件。如果回到任务组件,我们依然有问题。
正如droppable
,draggable
期望它的子组件是函数。第一个函数参数是provided
对象,它就像之前在droppable
的provided
对象。
provided
对象有一个属性叫 draggableProps
,它们要用在需要拖拽的组件,当用户输入时,组件可以作出回应。provided
还有另一个属性叫 dragHandleProps
。这些props要用在控制拖拽部分,因而使这部分可以控制整个组件。
你可以利用这点,只有组件的一小部分来拖动大的组件。在我们的应用里,我们想要整个任务可以拖动,所以把props应用至同一元素。正如droppable
,我们要提供ref
给draggable
。
现在看下我们的应用。我们可以不用滑鼠和键盘来拖动项目。这真不错。当我们拖动项目,我们可以看见它背面的内容。这看起来不太好。
回到我们的任务组件。增加背景色。白色就可以了。现在移动项目时,我们不会看到背后的内容。好啦,我们有看起来不错的重新排序的经验。
你可能注意当我拖动时,排序没有保留下来。为了实现持久化,我们要在onDragEnd
回调函数中实现我们的排序逻辑。
利用 onDragEnd
回调持久化列表排序
这节课展示怎样把拖拽互动的结果持久化
要实现效果,你将实现 onDragEnd
回调函数,它会在每次拖拽结束时调用。函数接收一个result
对象。我们会使用source
和destination
属性来更收任务列表的顺序
代码:codesandbox.io/embed/githu…
现在我们可以拖拽任务列表的内容,但互动的结果没有持久化,所以在拖放完成后,任务列表又返回原本的状态。我们得要在onDragEnd
函数完成持久化。
首先,什么是result
对象?我们被给予什么信息?这里就有例子,result
对象。result
有draggableId
,它是用户拖动的元素id。它有类型属性,它会之后再探讨,还有拖放原因,它会是放下或取消。
一般来说,明确知道用户是拖放或用取消键取消不是非常重要,但你如有需要,可以获取信息。我们现在用两个真的重要的对象,source
和destination
对象。
这些对象含有位置信息,拖动的开始和结束位置。在这项目中,draggable
组件开始于column-1
,索引为0,结束于column-1
和索引为1。destination
可能为null,例如用户拖出列表。
你可以从result
对象获取我们感兴趣的信息。如果没有destination
,没有什么需要做,我们可以直接退出。
我希望增加一些小检查,去校验拖动位置的变更。通过检查source
和destination
的draggableId
,还有它们的索引是否是一致,可以实现效果。如果两个校验都为真,表示用户把拖动项目放回原来位置,什么都不用做了。
我们需要重新排序列的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-dnd
的snapshot Values
自定义应用的外观
<Draggable />
和<Droppable />
组件提拱它们子函数的快照对象。当拖动发生时,快照对象可以用来创造美丽的视觉队列
代码:codesandbox.io/embed/githu…
任务列表重新排序看起来真不错。如果我们想当拖动时,添加样式?react-beautiful-dnd
提拱大量信息关于拖动互动的状态,它可以帮助你添加视觉效果。
你可以更新任何事物,当你拖动时,这不会改变你的draggable
和droppable
组件维度。改变组件的背景色是一个不错的方式去指明什么在移动和什么最近被拖动。
我们要怎样做呢?回到任务组件,组件函数的第二个参数是snapshot
,它包含一系列属性,让你可以在拖动时,添加样式。
我们有 draggable
的 snapshot
对象。snapshot
里有两个属性。第一个是布尔flagisDragging
,当draggable
被拖动时,它的值为真。另一个是draggingOver
,当draggable
被拖入droppable
区域时,它是一个droppable
的id。如果draggable
在拖动,没有进入droppable
区域时,draggingOver
是null
。
当任务拖动时,我们将用snapshot
来改变任务的背景色。我现在所做的是把isDragging
作为句prop,传入给我的样式组件,它有snapshot.isDragging
属性的值。
利用样式组件的api,我可以根据prop来设定组件的背景色。如果isDragging
为真,我想把背景色设为亮绿,否则为白色。
现在当我们拖动任务,拖动物件的背景色变为亮绿。我们也可以在droppable
实现同样效果,droppable
函数的第二个参数也是snapshot
。
droppable
的 snapshot
有两个属性。首先,它是一个布尔flag,叫isDraggingOver
,当draggable
拖入droppable
的区域时为真。另一属性是draggingOverWith
,它是进入droppable
的draggable
的id。如果没有任何draggable
在droppable
区域,它的值是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-direction
为column
。
子元素现在垂直排列。对于我们的任务列表,我想要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
哪裡可以放。
最简单的机制是用Droppable
的 type
prop,你可以提供可选类型给Droppable
。一个Draggable
只能拖入同一类型的Droppable
。
我根据列的id,选择性给Droppable
类型。第三列有Done
类型,但首两列有Active
类型。
这设置允许我在To-Do
和In-Progress
列间移动,但不能移到Done
列。第二个机制可以控制在哪可以拖放是Droppable
的isDropDisabled
。
如果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
组件。
这方法包含嵌套使用 draggable
和 droppable
。我们把列水平排序,任务垂直排序。
稍微改变初始数据,把第三列移除,因而比较容易看到效果。从 react-beautiful-dnd
导入droppable
,创造水平排序的droppable
。
我用一个droppable
包裹我们的容器组件。它的id是什么不重要,因为它不会与其他的droppable
有互动。称呼它为所有列(all-columns)吧。
我们需要把droppable
的方向设定为水平,使我们的列是水平排序。另外,我们也要用列的值增加类型prop。给all-columns
的droppable
不同的类型,使它不会与任务列表的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,插入到新的位置。
我要创造一个新的状态对象,它与旧的一样,但有新的列排序数组。回到我们的应用。列的重新排序结果是保存下来。然而,我拖动时,依然可以看到列头部背后的内容。
我没有只为标题加上背景色,我为整个列加上背景色。我要改变任务列务,加上条件判断,设定背景色,它的背景色不是设为白色,而是继承容器的白色。
现在我拖动列,你没有看到它背后的内容。我们现在可以重新排序任务,移动任务到其他列,排序列。我们也可以用键盘来操作。
利用 shouldComponentUpdate
和 PureComponent
来优化性能
我们可以不用重新渲染 Draggable
和 Droppable
组件更新样式,通过构建 <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的互动,但我觉得内容不太重要,就不翻译了,感兴趣可以自己去看看)