Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React DnD 预览 #36

Open
monsterooo opened this issue Aug 1, 2019 · 0 comments
Open

React DnD 预览 #36

monsterooo opened this issue Aug 1, 2019 · 0 comments
Labels

Comments

@monsterooo
Copy link
Owner

React DnD
GitHub - mzabriskie/react-draggable: React draggable component

预览

  • DnD在内部使用Redux保存数据

  • 后端(Backends):
    在DnD中你可以使用可插拔的方法去实现不同平台的拖动功能,在web上一般使用HTML5的drag drop,这种可插拔的方式在DnD中叫做后端。

  • 项目和类型(Items and Types):
    在DnD中使用数据而不是视图作为真实的来源,当在屏幕上拖动某些内容的时候,我们不会说正在拖动组件或DOM节点。相反,我们说某个类型(type)的项目(item)正在被拖动。

什么是项目(item)?项目(item)是一个简单的javascript对象,用于描述被拖动的内容。例如在看板应用程序中,当你拖动卡片是,项目可能看起来像{cardId: 42}。将拖动的数据描述为普通的对象有助于保持组件分离并且彼此不知道

  • 监视器(Monitors):
    拖动本质上是有状态的。要么拖动操作正在进行,要么没有。要么有当前类型(type)和当前项目(item),要么没有。这种state必须存在某个地方。

DnD通过称为监视器(monitors)的内部状态存储上的几个小包装器将此状态公开给组件。监视器允许你更新组件的poprs以相应拖动状态更改。

假设你想在拖动棋子时突出显示象棋单元格。Cell组件的收集函数(collecting function)可能就像下面这样:

function collect(monitor) {
  return {
    highlighted: monitor.canDrop(),
    hovered: monitor.isOver(),
  }
}

它指示DnD将突出显示的最新值作为props传递给所有Cell实例

  • 连接器(Connectors):
    如果后端处理DOM事件,但是组件使用React来描述DOM,name后端你如何指定要侦听那些DOM节点呢?使用连接器(connectors)。连接器(connectors)允许你讲预定义的角色(拖动源(drag source)、拖动预览(drag preview)或放置目标(drop target))分配给渲染功能中的DOM节点。

实际上,连接器(connectors)作为我们上面描述的收集函数的第一个参数传递。让我们来看看如何使用它来指定放置目标

// collect函数的第一个参数就是连接器
function collect(connect, monitor) {
  return {
    highlighted: monitor.canDrop(),
    hovered: monitor.isOver(),
    connectDropTarget: connect.dropTarget(),
  }
}

在组件render方法中,我们即可以访问从监视器(monitor)获得的数据和从连接器(connector)获得的函数:

render() {
  const { highlighted, hovered, connectDropTarget } = this.props;

  return connectDropTarget(
    <div className={classSet({
      'Cell': true,
      'Cell--highlighted': highlighted,
      'Cell--hovered': hovered
    })}>
      {this.props.children}
    </div>
  );
}

connectDropTarget调用告诉DnD我们的根DOM节点是一个有效的放置(drop)目标,并且它的hover和drop事件应该由后端处理。在内部,它通过将一个回调引用附加到你给它的React元素来工作。连接器返回的函数是memoized,因此它不会破坏shouldComponentUpdate优化。

  • 拖动源和放置目标(Drag Sources and Drop Targets)
    每当你想要使组件或其某部分可拖动时,你需要将该组件包装到拖动源(drag source)申明中。每个拖动都注册了某种类型(type),并且必须实现从组件的props中生成项目(item)的方法。它还可以选择指定一些其他方法来处理拖放时间。拖动源(drag source)声明还允许你指定给定组件的收集函数(collecting function)

  • 高阶组件(Higher-Order)和装饰器(Decorators)
    你如何包装组件?包装是什么意思?如果你以前没有使用过高阶组件,请继续阅读本文,因为它详细解释了这个概念。

高阶组件组件只是一个函数,它接收一个React组件,并返回另一个React组件类。库提供的包装组件在其render方法中呈现组将并将props转发给它,但也添加了一些有用的行为。

在DnD中,DragSourceDragTarget以及一些其他顶级导出函数实际上是高阶组件。它们向组件中注入拖放魔法。

使用它们的一个提示是,它们必须需要两个函数。例如,一下是如何在DragSource中包装YourComponent

import { DragSource } from 'react-dnd'

class YourComponent {
  /* ... */
}

export default DragSource(/* ... */)(YourComponent)

请注意,在第一个函数调用中指定DragSource参数之后,还有第二个函数调用,第二个函数调用中传递你的类(组件)。这叫做curryingpartial application,并且是装饰器语法开箱即用的必要条件:

import { DragSource } from 'react-dnd'

@DragSource(/* ... */)
export default class YourComponent {
  /* ... */
}

你不需要使用这种装饰器语法,如果你喜欢它,你可以使用Babel来转换你的代码,并将{ "stage": 1 }放入.babelrc文件来启用它。

即使你不打算使用装饰器,部分应用程序仍然可以使用,因为你可以使用如_.flow在javascript中组合多个DragSourceDropTarget声明。

import { DragSource, DropTarget } from 'react-dnd'
import flow from 'lodash/flow'

class YourComponent {
  render() {
    const { connectDragSource, connectDropTarget } = this.props
    return connectDragSource(
      connectDropTarget(),
      /* ... */
    )
  }
}

export default flow(
  DragSource(/* ... */),
  DropTarget(/* ... */),
)(YourComponent)
  • 将他们放在一起
    下面是将现有的Card组件包装到拖动源中的示例。
import React from 'react'
import { DragSource } from 'react-dnd'

// Drag sources和drag targets只有在具有相同的字符串类型是才交互
const Types = {
  CARD: 'card',
}

/**
 * 指定drag source 合约
 * 只需要 `beginDrag` 功能
 */
const cardSource = {
  beginDrag(props) {
    // 返回描述拖动项目(item)的数据
    const item = { id: props.id }
    return item
  },

  endDrag(props, monitor, component) {
    if (!monitor.didDrop()) {
      return
    }

    // 做一些拖动操作处理
    const item = monitor.getItem()
    const dropResult = monitor.getDropResult()
    CardActions.moveCardToList(item.id, dropResult.listId)
  },
}

/**
 * 指定要注入到组件的props
 */
function collect(connect, monitor) {
  return {
    // 在render()中调用此函数,让DnD处理拖动事件
    connectDragSource: connect.dragSource(),
    // 你还可以通过监视器获取当前拖动状态
    isDragging: monitor.isDragging(),
  }
}

function Card(props) {
  // 你的组件可以像以前一样收到props
  const { id } = props

  // 这两个props由DnD注入,由上面的`collect`函数定义
  const { isDragging, connectDragSource } = props

  return connectDragSource(
    <div>
      I am a draggable card number {id}
      {isDragging && ' (and I am being dragged now)'}
    </div>,
  )
}

// 导出包装的版本
export default DragSource(Types.CARD, cardSource, collect)(Card)

本章预览部分完。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant