React Dnd 是 redux 作者 Dan 另外一个非常赞的项目, dnd 是 Drag and Drop 的意思,为什么他会开发react-dnd 的项目,这个拖放库解决什么问题,和html 原生Drap Drop API 有什么样的联系和不同,设计是否有独特之处?让我们带着这些问题了解一下React Dnd吧.
React Dnd 是什么?
Reat Dnd 是React 和 Redux 的核心作者 Dan Abramov 创造的一组React 高阶组件,可以在保持组件分离的前提下帮助构建复杂的拖放接口。非常适合做类似jira 之类的应用程序,其中拖动在应用程序的不同部分之间传递数据,并且组件会根据拖放更改其外观和应用程序状态.
React Dnd 的出发点
现有拖放插件的问题:
- jquery 插件思维模式,直接改变DOM
- 拖放状态改变的影响不仅限于 CSS 类这种改变,不支持更加自定义
Html5 拖放API 的问题
- 不支持移动端
- 拖动预览问题
- 无法开箱即用
React Dnd 的需求
- 默认使用Html 拖放API.
- 不支持操作dom.
- Dom 和拖放的源和目标解耦
- 融入HTML5 拖放中获取类型匹配和数据传递的思想.
React Dnd 的特点:
专注拖拽,不提供现成组件
React DnD提供了一组强大的API,但它不包含任何现成组件,而是采用包裹使用者的组件并注入 props 的方式。 它比jQuery UI等更底层,专注于使拖放交互正确,而把视觉方面的效果例如坐标限制交给使用者处理。这其实是一种关注点分离的原则,例如React DnD不打算提供可排序组件,但是使用者可以基于它快速开发任何需要的自定义的可排序组件。
单向数据流
类似于 React 一样采取声明式渲染,并且像 redux 一样采用单向数据流架构,实际上内部使用了 Redux.
隐藏了平台底层API的问题
HTML5拖放API充满了陷阱和浏览器的不一致。 React DnD为您内部处理它们,因此使用者可以专注于开发应用程序而不是解决浏览器问题。
可扩展可测试
React DnD默认提供了HTML5拖放API封装,但它也允许您提供自定义的“后端(backend)”。您可以根据触摸事件,鼠标事件或其他内容创建自定义DnD后端。例如,内置的模拟后端允许您测试Node环境中组件的拖放交互。
React Dnd 基本用法:
下面使用 React Dnd 做的一个TaskCard 的应用:
TaskCard 拖拽源代码如下:
import { Box, Badge, Text, Flex } from '@chakra-ui/core';
import { ItemTypes } from '../utils/items';
import { useDrag } from 'react-dnd';
const TaskCard = props => {
const [{ isDragging }, drag] = useDrag({
item: {
type: ItemTypes.CARD,
id: props._id,
},
collect: monitor => ({
isDragging: !!monitor.isDragging(),
}),
});
return (
<Box
ref={drag}
my='4'
p={3}
bg='gray.500'
opacity={isDragging ? '0.5' : '1'}
boxShadow='sm'
w='100%'
rounded='md'
color='white'>
<Flex justify='space-between' my='2'>
<Text fontSize='lg' fontWeight='semibold'>
{props.title}
</Text>
<Badge
variantColor={props.category === 'Chores' ? 'green' : 'red'}
h='100%'>
{props.category}
</Badge>
</Flex>
<Text textAlign='center' fontSize='md'>
{props.details}
</Text>
</Box>
);
};
export default TaskCard;
react-dnd 封装了一个 useDrag 的hook ,暴露出 isDragging,drag 的属性。
放置目标的代码如下:
import { Box } from '@chakra-ui/core';
import { useDrop } from 'react-dnd';
import { ItemTypes } from '../utils/items';
import { useContext } from 'react';
import { CardContext } from '../pages/tasks';
const BoxTarget = props => {
const { markAsDone } = useContext(CardContext);
const [{ isOver }, drop] = useDrop({
accept: ItemTypes.CARD,
drop: (item, monitor) => {
console.log('item',item);
return markAsDone(item.id);
},
collect: monitor => ({
isOver: !!monitor.isOver(),
}),
});
return (
<Box
ref={drop}
m={2}
p={3}
boxShadow='sm'
bg={isOver ? 'green.500' : 'green.200'}
minH='200px'
textAlign='center'
w='100%'
rounded='md'
color='white'>
{props.children}
</Box>
);
};
export default BoxTarget;
react-dnd 封装了一个 useDrop 的hook ,暴露出 drop 的属性。
源码地址: https://github.com/ms-mousa/Chakra.git
React Dnd 整体架构图:
dnd-core
核心层主要用来实现拖放
- 实现了拖放管理器,定义了拖放的交互
- 和框架无关,你可以基于它结合 react、jquery、RN等技术开发
- 内部依赖了 redux 来管理状态
- 实现了 DragDropManager,连接 Backend 和 Monitor
- 实现了 DragDropMonitor,从 store 获取状态,同时根据store的状态和自定义的状态获取函数来计算最终的状态
- 实现了 HandlerRegistry 维护所有的 types
- 定义了 Backend , DropTarget , DragSource 等接口
- 工厂函数 createDragDropManager 用来接收传入的 backend 来创建一个管理器
react-dnd
上层 React 版本的Drag and Drop的实现
- 提供 hooks、decorators API.
- 通过业务层获取 backend 实现和组件来给核心层工厂函数
- 通过核心层获取状态传递给业务层
backend-html5
- 主要暴露了一个工厂函数,传入 manager 来获取 HTML5Backend 实例
- HTML5Backend 实现了 Backend 接口
参考资料:
React DnD 拖放库浅析 https://www.jianshu.com/p/81c1735b1944
The Future of Drag and Drop APIs https://medium.com/@dan_abramov/the-future-of-drag-and-drop-apis-249dfea7a15f