2019-02-14 11:19:23 wangweiren_get 阅读数 140
  • 《C语言/C++学习指南》Qt界面开发篇视频精讲

    本篇讲解如何使用Qt库进行界面开发,是一个从入门到精通的教程。主要的演示平台为VS2008 + Qt4.7.3,从简单的例子入手,覆盖Qt界面开发的近乎全部技术点。(自定义Widget、对话框、事件处理Singal & Slot、界面线程与工作线程、数据视图ItemView、拖放操作Drag & Drop、界面美化Qt Style等等) 提供全部示例代码的下载。 (前四章免费)

    69539 人正在学习 去看看 邵发

react-drag记录

/*
	bound 为限制位置
*/
'use strict';

var React = require('react');
var findDOMNode = require('react-dom').findDOMNode;
var propTypes = require('prop-types');
var createReactClass = require('create-react-class');

function classNames() {
  var classes = '';
  for (var i = 0; i < arguments.length; i++) {
    var arg = arguments[i];
    if (!arg) continue;
    var argType = typeof arg;
    if ('string' === argType || 'number' === argType) {
      classes += ' ' + arg;
    } else if (Array.isArray(arg)) {
      classes += ' ' + classNames.apply(null, arg);
    } else if ('object' === argType) {
      for (var key in arg) {
        if (arg.hasOwnProperty(key) && arg[key]) {
          classes += ' ' + key;
        }
      }
    }
  }
  return classes.substr(1);
}

var emptyFunction = function () {};
var CX = classNames;

function createUIEvent(draggable) {
  return {
    position: {
      top: draggable.state.pageY,
      left: draggable.state.pageX
    }
  };
}

function canDragY(draggable) {
  return draggable.props.axis === 'both' ||
      draggable.props.axis === 'y';
}

function canDragX(draggable) {
  return draggable.props.axis === 'both' ||
      draggable.props.axis === 'x';
}

function isFunction(func) {
  return typeof func === 'function' ||
    Object.prototype.toString.call(func) === '[object Function]';
}

// @credits https://gist.github.com/rogozhnikoff/a43cfed27c41e4e68cdc
function findInArray(array, callback) {
  for (var i = 0, length = array.length, element = null; i < length; i += 1) {
    element = array[i];
    if (callback.apply(callback, [element, i, array])) {
      return element;
    }
  }
}

function matchesSelector(el, selector) {
  var method = findInArray([
    'matches',
    'webkitMatchesSelector',
    'mozMatchesSelector',
    'msMatchesSelector',
    'oMatchesSelector'
  ], function (method) {
    return isFunction(el[method]);
  });

  return el[method].call(el, selector);
}

// @credits:
// http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript/4819886#4819886
/* Conditional to fix node server side rendering of component */
var isTouchDevice;
if (typeof window === 'undefined') {
    // Do Node Stuff
  isTouchDevice = false;
} else {
  // Do Browser Stuff
  isTouchDevice = 'ontouchstart' in window // works on most browsers
    || 'onmsgesturechange' in window; // works on ie10 on ms surface
  // Check for IE11
  try {
    document.createEvent('TouchEvent');
  } catch (e) {
    isTouchDevice = false;
  }

}

// look ::handleDragStart
//function isMultiTouch(e) {
//  return e.touches && Array.isArray(e.touches) && e.touches.length > 1
//}

/**
 * simple abstraction for dragging events names
 * */
var dragEventFor = (function () {
  var eventsFor = {
    touch: {
      start: 'touchstart',
      move: 'touchmove',
      end: 'touchend'
    },
    mouse: {
      start: 'mousedown',
      move: 'mousemove',
      end: 'mouseup'
    }
  };
  return eventsFor[isTouchDevice ? 'touch' : 'mouse'];
})();

/**
 * get {pageX, pageY} positions of control
 * */
function getControlPosition(e) {
  var position = (e.touches && e.touches[0]) || e;
  return {
    pageX: position.pageX,
    pageY: position.pageY
  };
}

function getBoundPosition(pageX, pageY, bound, target) {
  if (bound) {
    if ((typeof bound == 'string' && bound.toLowerCase() !== 'parent') &&
        (typeof bound !== 'object')) {
      console.warn('Bound should either "parent" or an object');
    }
    var par = target.parentNode;
    var topLimit = bound.top || 0;
    var leftLimit = bound.left || 0;
    var rightLimit = bound.right || par.offsetWidth;
    var bottomLimit = bound.bottom || par.offsetHeight;
    pageX = Math.min(pageX, rightLimit - target.offsetWidth);
    pageY = Math.min(pageY, bottomLimit - target.offsetHeight);
    pageX = Math.max(leftLimit, pageX);
    pageY = Math.max(topLimit, pageY);
  }
  return {
    pageX: pageX,
    pageY: pageY
  };
}

function addEvent(el, event, handler) {
  if (!el) { return; }
  if (el.attachEvent) {
    el.attachEvent('on' + event, handler);
  } else if (el.addEventListener) {
    el.addEventListener(event, handler, true);
  } else {
    el['on' + event] = handler;
  }
}

function removeEvent(el, event, handler) {
  if (!el) { return; }
  if (el.detachEvent) {
    el.detachEvent('on' + event, handler);
  } else if (el.removeEventListener) {
    el.removeEventListener(event, handler, true);
  } else {
    el['on' + event] = null;
  }
}

var ReactDrag = createReactClass({
  displayName: 'Draggable',

  propTypes: {
    /**
     * `axis` determines which axis the draggable can move.
     *
     * 'both' allows movement horizontally and vertically.
     * 'x' limits movement to horizontal axis.
     * 'y' limits movement to vertical axis.
     *
     * Defaults to 'both'.
     */
    axis: propTypes.oneOf(['both', 'x', 'y']),

    /**
     * `handle` specifies a selector to be used as the handle
     * that initiates drag.
     *
     * Example:
     *
     * ```jsx
     *   var App = React.createClass({
     *       render: function () {
     *         return (
     *            <Draggable handle=".handle">
     *              <div>
     *                  <div className="handle">Click me to drag</div>
     *                  <div>This is some other content</div>
     *              </div>
     *           </Draggable>
     *         );
     *       }
     *   });
     * ```
     */
    handle: propTypes.string,

    /**
     * `cancel` specifies a selector to be used to prevent drag initialization.
     *
     * Example:
     *
     * ```jsx
     *   var App = React.createClass({
     *       render: function () {
     *           return(
     *               <Draggable cancel=".cancel">
     *                   <div>
     *             <div className="cancel">You can't drag from here</div>
     *            <div>Dragging here works fine</div>
     *                   </div>
     *               </Draggable>
     *           );
     *       }
     *   });
     * ```
     */
    cancel: propTypes.string,

    /**
     * `grid` specifies the x and y that dragging should snap to.
     *
     * Example:
     *
     * ```jsx
     *   var App = React.createClass({
     *       render: function () {
     *           return (
     *               <Draggable grid={[25, 25]}>
     *                   <div>I snap to a 25 x 25 grid</div>
     *               </Draggable>
     *           );
     *       }
     *   });
     * ```
     */
    grid: propTypes.arrayOf(propTypes.number),

    /**
     * `start` specifies the x and y that the dragged item should start at
     *
     * Example:
     *
     * ```jsx
     *   var App = React.createClass({
     *       render: function () {
     *           return (
     *               <Draggable start={{x: 25, y: 25}}>
     *                   <div>I start with left: 25px; top: 25px;</div>
     *               </Draggable>
     *           );
     *       }
     *   });
     * ```
     */
    start: propTypes.object,

    /**
     * Called when dragging starts.
     *
     * Example:
     *
     * ```js
     *  function (event, ui) {}
     * ```
     *
     * `event` is the Event that was triggered.
     * `ui` is an object:
     *
     * ```js
     *  {
     *    position: {top: 0, left: 0}
     *  }
     * ```
     */
    onStart: propTypes.func,

    /**
     * Called while dragging.
     *
     * Example:
     *
     * ```js
     *  function (event, ui) {}
     * ```
     *
     * `event` is the Event that was triggered.
     * `ui` is an object:
     *
     * ```js
     *  {
     *    position: {top: 0, left: 0}
     *  }
     * ```
     */
    onDrag: propTypes.func,

    /**
     * Called when dragging stops.
     *
     * Example:
     *
     * ```js
     *  function (event, ui) {}
     * ```
     *
     * `event` is the Event that was triggered.
     * `ui` is an object:
     *
     * ```js
     *  {
     *    position: {top: 0, left: 0}
     *  }
     * ```
     */
    onStop: propTypes.func,

    /**
     * A workaround option which can be passed if
     * onMouseDown needs to be accessed,
     * since it'll always be blocked (due to that
     * there's internal use of onMouseDown)
     *
     */
    onMouseDown: propTypes.func,

    /**
     * Defines the bounderies around the element
     * could be dragged. This property could be
     * object or a string. If used as object
     * the bounderies should be defined as:
     *
     * {
     *   left: LEFT_BOUND,
     *   right: RIGHT_BOUND,
     *   top: TOP_BOUND,
     *   bottom: BOTTOM_BOUND
     * }
     *
     * The only acceptable string
     * property is: "parent".
     */
    bound: propTypes.any
  },

  componentWillUnmount: function () {
    // Remove any leftover event handlers
    removeEvent(window, dragEventFor.move, this.handleDrag);
    removeEvent(window, dragEventFor.end, this.handleDragEnd);
  },

  getDefaultProps: function () {
    return {
      axis: 'both',
      handle: null,
      cancel: null,
      grid: null,
      bound: false,
      start: {
        x: 0,
        y: 0
      },
      onStart: emptyFunction,
      onDrag: emptyFunction,
      onStop: emptyFunction,
      onMouseDown: emptyFunction
    };
  },

  getInitialState: function () {
    return {
      // Whether or not currently dragging
      dragging: false,

      // Start top/left of this.getDOMNode()
      startX: 0,
      startY: 0,

      // Offset between start top/left and mouse top/left
      offsetX: 0,
      offsetY: 0,

      // Current top/left of this.getDOMNode()
      pageX: this.props.start.x,
      pageY: this.props.start.y
    };
  },

  handleDragStart: function (e) {
    // todo: write right implementation to prevent multitouch drag
    // prevent multi-touch events
    // if (isMultiTouch(e)) {
    //     this.handleDragEnd.apply(e, arguments);
    //     return
    // }

    // Make it possible to attach event handlers on top of this one
    this.props.onMouseDown(e);

    var node = findDOMNode(this);

    // Short circuit if handle or cancel prop was provided
    // and selector doesn't match
    if ((this.props.handle && !matchesSelector(e.target, this.props.handle)) ||
      (this.props.cancel && matchesSelector(e.target, this.props.cancel))) {
      return;
    }

    var dragPoint = getControlPosition(e);

    // Initiate dragging
    this.setState({
      dragging: true,
      offsetX: parseInt(dragPoint.pageX, 10),
      offsetY: parseInt(dragPoint.pageY, 10),
      startX: parseInt(node.style.left, 10) || 0,
      startY: parseInt(node.style.top, 10) || 0
    });

    // Call event handler
    this.props.onStart(e, createUIEvent(this));

    // Add event handlers
    addEvent(window, dragEventFor.move, this.handleDrag);
    addEvent(window, dragEventFor.end, this.handleDragEnd);
  },

  handleDragEnd: function (e) {
    // Short circuit if not currently dragging
    if (!this.state.dragging) {
      return;
    }

    // Turn off dragging
    this.setState({
      dragging: false
    });

    // Call event handler
    this.props.onStop(e, createUIEvent(this));

    // Remove event handlers
    removeEvent(window, dragEventFor.move, this.handleDrag);
    removeEvent(window, dragEventFor.end, this.handleDragEnd);
  },

  handleDrag: function (e) {
    var dragPoint = getControlPosition(e);

    // Calculate top and left
    var pageX = (this.state.startX +
        (dragPoint.pageX - this.state.offsetX));
    var pageY = (this.state.startY +
        (dragPoint.pageY - this.state.offsetY));
    var pos =
      getBoundPosition(pageX, pageY, this.props.bound, findDOMNode(this));
    pageX = pos.pageX;
    pageY = pos.pageY;

    // Snap to grid if prop has been provided
    if (Array.isArray(this.props.grid)) {
      var directionX = pageX < parseInt(this.state.pageX, 10) ? -1 : 1;
      var directionY = pageY < parseInt(this.state.pageY, 10) ? -1 : 1;

      pageX = Math.abs(pageX - parseInt(this.state.pageX, 10)) >=
          this.props.grid[0]
          ? (parseInt(this.state.pageX, 10) +
            (this.props.grid[0] * directionX))
          : this.state.pageX;

      pageY = Math.abs(pageY - parseInt(this.state.pageY, 10)) >=
          this.props.grid[1]
          ? (parseInt(this.state.pageY, 10) +
            (this.props.grid[1] * directionY))
          : this.state.pageY;
    }

    // Update top and left
    this.setState({
      pageX: pageX,
      pageY: pageY
    });

    // Call event handler
    this.props.onDrag(e, createUIEvent(this));

    // Prevent the default behavior
    e.preventDefault();
  },

  render: function () {
    var originalStyle = this.props.children.props.style;
    var style = {
      // Set top if vertical drag is enabled
      top: canDragY(this)
        ? this.state.pageY
        : this.state.startY,

        // Set left if horizontal drag is enabled
      left: canDragX(this)
        ? this.state.pageX
        : this.state.startX
    };
    for (var s in originalStyle) {
      style[s] = originalStyle[s];
    }
    var className = CX({
      'react-drag': true,
      'react-drag-dragging': this.state.dragging
    });
    var oldClass = this.props.children.props.className;
    if (oldClass) {
      className = oldClass + ' ' + className;
    }
    // Reuse the child provided
    // This makes it flexible to use whatever element is wanted (div, ul, etc)
    return React.cloneElement(
        React.Children.only(this.props.children), {
      style: style,
      className: className,

      onMouseDown: this.handleDragStart,
      onTouchStart: function (ev) {
        ev.preventDefault(); // prevent for scroll
        return this.handleDragStart.apply(this, arguments);
      }.bind(this),

      onMouseUp: this.handleDragEnd,
      onTouchEnd: this.handleDragEnd
    });
  }
});

module.exports = ReactDrag;

thanks

2019-11-26 14:55:06 a_wtf_pgm 阅读数 87
  • 《C语言/C++学习指南》Qt界面开发篇视频精讲

    本篇讲解如何使用Qt库进行界面开发,是一个从入门到精通的教程。主要的演示平台为VS2008 + Qt4.7.3,从简单的例子入手,覆盖Qt界面开发的近乎全部技术点。(自定义Widget、对话框、事件处理Singal & Slot、界面线程与工作线程、数据视图ItemView、拖放操作Drag & Drop、界面美化Qt Style等等) 提供全部示例代码的下载。 (前四章免费)

    69539 人正在学习 去看看 邵发

最近工作中遇到一个需求,需要对管理系统中的图片进行拖拽排序的功能

具体用到了react-dnd来实现

GIthub地址:https://github.com/goccult/drag-sort-test

 

下面是一个利用react-dnd实现的拖拽排序小Demo

首先在react项目中执行以下命令

yarn add react-dnd

yarn add react-dnd-html5-backen

// CardTable.js
import CardItem from './CardItem'
import HTML5Backend from 'react-dnd-html5-backend'
import React from 'react';
import { DndProvider } from 'react-dnd'
import './CardTable.less';

class CardTable extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      cardList: [],
      newRender: false,
    }
  }

  // 初始化数据
  componentDidMount = () => {
    let cardList = []
    for(let i = 0; i < 32; i++) {
      cardList.push(i)
    }
    this.setState({
      cardList,
    })
  }

  // 卡片重新排序
  moveSort = (dragIndex, hoverIndex) => {
    const cardList = this.state.cardList
    const tmp = cardList[dragIndex]
    cardList.splice(dragIndex, 1)
    cardList.splice(hoverIndex, 0, tmp)
    this.setState({
      cardList,
    })
  }

  // 卡片重新render
  newRenderFn = () => {
    this.setState({
      newRender: !this.state.newRender,
    })
  }

  render() {
    return (
      // 使用dndprovider包裹可被拖拽的卡片
      <DndProvider backend={HTML5Backend}>
        <div className="CardTable">
          {
            this.state.cardList.map((value, index) => {
              return <CardItem 
                key={index} 
                index={index} 
                value={value}
                newRender={this.state.newRender}
                moveSort={this.moveSort}
                newRenderFn={this.newRenderFn}
              />
            })
          }
        </div>
      </DndProvider>
    );
  }
}

export default CardTable;
// CardTable.less
.CardTable {
  width: 700px;
  // max-height: 800px;
  overflow: hidden;
  display: grid;
  flex-wrap: wrap;
  justify-content: center;
  grid-template-columns: repeat(auto-fill, 100px);
  grid-gap: 20px 30px;
  grid-template-rows: 100px;
  margin: auto;
}

 

// CardItem.js

import React from 'react';
import { DragSource, DropTarget } from 'react-dnd'
import { findDOMNode } from 'react-dom'
import './CardItem.less'

let dragingIndex = '-1'
let opacity = '1'

// 被拖拽的卡片
const CardSource = {
  // 卡片开始被拖拽时触发
  beginDrag(props) {
    dragingIndex = props.value
    return {
      index: props.index,
    }
  },
  // 卡片结束拖拽时触发
  endDrag(props) {
    dragingIndex = '-1'
    opacity = '1'
    props.newRenderFn() 
  },
}

// 目标卡片
const CardTarget = {
  hover(props, monitor, component) {
    const dragIndex = monitor.getItem().index
    const hoverIndex = props.index

    if(dragIndex === hoverIndex) return null
    const hoverBoundingRect = (findDOMNode(component)).getBoundingClientRect()
    const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2
    const clientOffset = monitor.getClientOffset()
    const hoverClientX = (clientOffset).x - hoverBoundingRect.left // 悬浮时鼠标距离目标源的左边距

    if(hoverIndex - dragIndex === 1 && hoverClientX < hoverMiddleX) {
      return null
    }
    if(dragIndex - hoverIndex === 1 && hoverClientX > hoverMiddleX) {
      return null
    }

    props.moveSort(dragIndex, hoverIndex)
    monitor.getItem().index = hoverIndex
  },
}

class CardItem extends React.Component {

  render() {
    const {connectDragSource, connectDropTarget, value} = this.props
    opacity = value === dragingIndex  ? '0.1' : '1'
    return (
      connectDragSource(connectDropTarget(
        <div className="CardItem" style={{opacity}}>
          {this.props.value}
        </div>
      ))
    );
  }
}

export default DragSource('card', CardSource, (connect, monitor) => ({
  connectDragSource: connect.dragSource(),
  isDragging: monitor.isDragging(),
}))(
  DropTarget('card', CardTarget, (connect, monitor) => ({
    connectDropTarget: connect.dropTarget(),
    isOver: monitor.isOver(),
  }))(CardItem),
)
// CardItem.less

.CardItem {
  width: 100px;
  height: 100px;
  background: rgb(0, 255, 255);
  cursor: move;
}

 

 

2018-06-14 16:02:50 hahahahahahahaha__1 阅读数 4369
  • 《C语言/C++学习指南》Qt界面开发篇视频精讲

    本篇讲解如何使用Qt库进行界面开发,是一个从入门到精通的教程。主要的演示平台为VS2008 + Qt4.7.3,从简单的例子入手,覆盖Qt界面开发的近乎全部技术点。(自定义Widget、对话框、事件处理Singal & Slot、界面线程与工作线程、数据视图ItemView、拖放操作Drag & Drop、界面美化Qt Style等等) 提供全部示例代码的下载。 (前四章免费)

    69539 人正在学习 去看看 邵发
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>drag--react</title>
	<script src='react.js'></script>
	<script src='react-dom.js'></script>
	<script src='babel.min.js'></script>
</head>
<style>
	*{
		margin:0;
		padding:0;
	}
</style>
<body>
	<div id='example'></div>
	<script type='text/babel'>
		/*拖拽组件*/
		class Drag extends React.Component{
			constructor(){
				super();
				this.state = {
					/*定义两个值用来存放当前元素的left和top值*/
					needX:0,
					needY:0
				}
				/*定义两个值用来存放鼠标按下的地方距离元素上侧和左侧边界的值*/
				this.disX = 0;
				this.disY = 0;
			}
			/*定义鼠标下落事件*/
			fnDown(e){
				/*事件兼容*/
				let event = e || window.event;
				/*事件源对象兼容*/
				let target = event.target || event.srcElement;
				/*获取鼠标按下的地方距离元素左侧和上侧的距离*/
				this.disX = event.clientX - target.offsetLeft;
				this.disY = event.clientY - target.offsetTop;
				/*定义鼠标移动事件*/
				document.onmousemove = this.fnMove.bind(this);
				/*定义鼠标抬起事件*/
				document.onmouseup = this.fnUp.bind(this);
			}
			/*定义鼠标移动事件*/
			fnMove(e){
				/*事件兼容*/
				let event = e|| window.event ;
				/*事件源对象兼容*/
				let target = event.target || event.srcElement;
				this.setState({
					needX:event.clientX - this.disX,
					needY:event.clientY - this.disY
				});
			}
			fnUp(){
				document.onmousemove = null;
				document.onmuseup = null;
			}
			render(){
				/*返回元素*/
				return (
					<div style={{width:this.props.style.width,height:this.props.style.height,backgroundColor:this.props.style.backgroundColor,position:this.props.style.position,left:this.state.needX,
					top:this.state.needY}} onMouseDown={this.fnDown.bind(this)}></div>
				)
			}
		}
		ReactDOM.render(
			/*渲染组件*/
			/*通过props属性进行传值*/
			<Drag style={{width:'100px',height:'100px',backgroundColor:'green',position:'absolute'}}/>,
			document.getElementById('example')
		);
	</script>
</body>
</html>

2019-09-03 17:59:59 weixin_40906515 阅读数 180
  • 《C语言/C++学习指南》Qt界面开发篇视频精讲

    本篇讲解如何使用Qt库进行界面开发,是一个从入门到精通的教程。主要的演示平台为VS2008 + Qt4.7.3,从简单的例子入手,覆盖Qt界面开发的近乎全部技术点。(自定义Widget、对话框、事件处理Singal & Slot、界面线程与工作线程、数据视图ItemView、拖放操作Drag & Drop、界面美化Qt Style等等) 提供全部示例代码的下载。 (前四章免费)

    69539 人正在学习 去看看 邵发

前言

你将在该篇学到:

  • 如何将现有组件改写为 React Hooks函数组件
  • useStateuseEffectuseRef是如何替代原生命周期和Ref的。
  • 一个完整拖拽上传行为覆盖的四个事件:dragoverdragenterdropdragleave
  • 如何使用React Hooks编写自己的UI组件库。

逛国外社区时看到这篇:

How To Implement Drag and Drop for Files in React

文章讲了React拖拽上传的精简实现,但直接翻译照搬显然不是我的风格。

于是我又用React Hooks 重写了一版,除CSS的代码总数 120行。
效果如下:

1. 添加基本目录骨架

app.js

import React from 'react';
import PropTypes from 'prop-types';

import { FilesDragAndDrop } from '../components/Common/FilesDragAndDropHook';

export default class App extends React.Component {
    static propTypes = {};

    onUpload = (files) => {
        console.log(files);
    };

    render() {
        return (
            <div>
                <FilesDragAndDrop
                    onUpload={this.onUpload}
                />
            </div>
        );
    }
}

FilesDragAndDrop.js(非Hooks):

import React from 'react';
import PropTypes from 'prop-types';

import '../../scss/components/Common/FilesDragAndDrop.scss';

export default class FilesDragAndDrop extends React.Component {
    static propTypes = {
        onUpload: PropTypes.func.isRequired,
    };

    render() {
        return (
            <div className='FilesDragAndDrop__area'>
                传下文件试试?
                <span
                    role='img'
                    aria-label='emoji'
                    className='area__icon'
                >
                    &#128526;
                </span>
            </div>
        );
    }
}

1. 如何改写为Hooks组件?

请看动图:

2. 改写组件

Hooks版组件属于函数组件,将以上改造:

import React, { useEffect, useState, useRef } from "react";
import PropTypes from 'prop-types';
import classNames from 'classnames';
import classList from '../../scss/components/Common/FilesDragAndDrop.scss';
const FilesDragAndDrop = (props) => {
    return (
        <div className='FilesDragAndDrop__area'>
            传下文件试试?
            <span
                role='img'
                aria-label='emoji'
                className='area__icon'
            >
                &#128526;
            </span>
        </div>
    );
}

FilesDragAndDrop.propTypes = {
    onUpload: PropTypes.func.isRequired,
    children: PropTypes.node.isRequired,
    count: PropTypes.number,
    formats: PropTypes.arrayOf(PropTypes.string)
}

export { FilesDragAndDrop };

FilesDragAndDrop.scss

.FilesDragAndDrop {
  .FilesDragAndDrop__area {
    width: 300px;
    height: 200px;
    padding: 50px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-flow: column nowrap;
    font-size: 24px;
    color: #555555;
    border: 2px #c3c3c3 dashed;
    border-radius: 12px;

    .area__icon {
      font-size: 64px;
      margin-top: 20px;
    }
  }
}

然后就可以看到页面:

2. 实现分析

从操作DOM、组件复用、事件触发、阻止默认行为、以及Hooks应用方面分析。

1. 操作DOM:useRef

由于需要拖拽文件上传以及操作组件实例,需要用到ref属性。

React Hooks 中 新增了useRef API
语法

const refContainer = useRef(initialValue);
  • useRef 返回一个可变的 ref 对象,。
  • .current 属性被初始化为传递的参数(initialValue
  • 返回的对象将存留在整个组件的生命周期中。
...
const drop = useRef();

return (
    <div
        ref={drop}
        className='FilesDragAndDrop'
    />
    ...
    )

2. 事件触发


完成具有动态交互的拖拽行为并不简单,需要用到四个事件控制:

  • 区域外:dragleave, 离开范围
  • 区域内:dragenter,用来确定放置目标是否接受放置。
  • 区域内移动:dragover,用来确定给用户显示怎样的反馈信息
  • 完成拖拽(落下):drop,允许放置对象。

这四个事件并存,才能阻止 Web 浏览器默认行为和形成反馈。

3. 阻止默认行为

代码很简单:

e.preventDefault() //阻止事件的默认行为(如在浏览器打开文件)
e.stopPropagation() // 阻止事件冒泡

每个事件阶段都需要阻止,为啥呢?举个?栗子:

const handleDragOver = (e) => {
    // e.preventDefault();
    // e.stopPropagation();
};

不阻止的话,就会触发打开文件的行为,这显然不是我们想看到的。

4. 组件内部状态: useState

拖拽上传组件,除了基础的拖拽状态控制,还应有成功上传文件或未通过验证时的消息提醒。
状态组成应为:

state = {
    dragging: false,
    message: {
        show: false,
        text: null,
        type: null,
    },
};

写成对应useState前先回归下写法:

const [属性, 操作属性的方法] = useState(默认值);

于是便成了:

const [dragging, setDragging] = useState(false);
const [message, setMessage] = useState({ show: false, text: null, type: null });

5. 需要第二个叠加层

除了drop事件,另外三个事件都是动态变化的,而在拖动元素时,每隔 350 毫秒会触发 dragover事件。

此时就需要第二ref来统一控制。

所以全部的`ref``为:

const drop = useRef(); // 落下层
const drag = useRef(); // 拖拽活动层

6. 文件类型、数量控制

我们在应用组件时,prop需要传入类型和数量来控制

<FilesDragAndDrop
    onUpload={this.onUpload}
    count={1}
    formats={['jpg', 'png']}
>
    <div className={classList['FilesDragAndDrop__area']}>
        传下文件试试?
<span
            role='img'
            aria-label='emoji'
            className={classList['area__icon']}
        >
            &#128526;
</span>
    </div>
</FilesDragAndDrop>
  • onUpload:拖拽完成处理事件
  • count: 数量控制
  • formats: 文件类型。

对应的组件Drop内部事件:handleDrop:

const handleDrop = (e) => {
    e.preventDefault();
    e.stopPropagation();
    setDragging(false)
    const { count, formats } = props;
    const files = [...e.dataTransfer.files];
    if (count && count < files.length) {
        showMessage(`抱歉,每次最多只能上传${count} 文件。`, 'error', 2000);
        return;
    }
    if (formats && files.some((file) => !formats.some((format) => file.name.toLowerCase().endsWith(format.toLowerCase())))) {
        showMessage(`只允许上传 ${formats.join(', ')}格式的文件`, 'error', 2000);
        return;
    }
    if (files && files.length) {
        showMessage('成功上传!', 'success', 1000);
        props.onUpload(files);
    }
};

.endsWith是判断字符串结尾,如:"abcd".endsWith("cd"); // true

showMessage则是控制显示文本:

const showMessage = (text, type, timeout) => {
    setMessage({ show: true, text, type, })
    setTimeout(() =>
        setMessage({ show: false, text: null, type: null, },), timeout);
};

需要触发定时器来回到初始状态

7. 事件在生命周期里的触发与销毁

原本EventListener的事件需要在componentDidMount添加,在componentWillUnmount中销毁:

componentDidMount () {
    this.drop.addEventListener('dragover', this.handleDragOver);
}

componentWillUnmount () {
    this.drop.removeEventListener('dragover', this.handleDragOver);
}

Hooks中有内部操作方法和对应useEffect来取代上述两个生命周期

useEffect示例:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

每个effect都可以返回一个清除函数。如此可以将添加(componentDidMount)和移除(componentWillUnmount) 订阅的逻辑放在一起。

于是上述就可以写成:

useEffect(() => {
    drop.current.addEventListener('dragover', handleDragOver);
    return () => {
        drop.current.removeEventListener('dragover', handleDragOver);
    }
})


这也太香了吧!!!

3. 完整代码:

FilesDragAndDropHook.js:

import React, { useEffect, useState, useRef } from "react";
import PropTypes from 'prop-types';
import classNames from 'classnames';
import classList from '../../scss/components/Common/FilesDragAndDrop.scss';

const FilesDragAndDrop = (props) => {
    const [dragging, setDragging] = useState(false);
    const [message, setMessage] = useState({ show: false, text: null, type: null });
    const drop = useRef();
    const drag = useRef();
    useEffect(() => {
        // useRef 的 drop.current 取代了 ref 的 this.drop
        drop.current.addEventListener('dragover', handleDragOver);
        drop.current.addEventListener('drop', handleDrop);
        drop.current.addEventListener('dragenter', handleDragEnter);
        drop.current.addEventListener('dragleave', handleDragLeave);
        return () => {
            drop.current.removeEventListener('dragover', handleDragOver);
            drop.current.removeEventListener('drop', handleDrop);
            drop.current.removeEventListener('dragenter', handleDragEnter);
            drop.current.removeEventListener('dragleave', handleDragLeave);
        }
    })
    const handleDragOver = (e) => {
        e.preventDefault();
        e.stopPropagation();
    };

    const handleDrop = (e) => {
        e.preventDefault();
        e.stopPropagation();
        setDragging(false)
        const { count, formats } = props;
        const files = [...e.dataTransfer.files];

        if (count && count < files.length) {
            showMessage(`抱歉,每次最多只能上传${count} 文件。`, 'error', 2000);
            return;
        }

        if (formats && files.some((file) => !formats.some((format) => file.name.toLowerCase().endsWith(format.toLowerCase())))) {
            showMessage(`只允许上传 ${formats.join(', ')}格式的文件`, 'error', 2000);
            return;
        }

        if (files && files.length) {
            showMessage('成功上传!', 'success', 1000);
            props.onUpload(files);
        }
    };

    const handleDragEnter = (e) => {
        e.preventDefault();
        e.stopPropagation();
        e.target !== drag.current && setDragging(true)
    };

    const handleDragLeave = (e) => {
        e.preventDefault();
        e.stopPropagation();
        e.target === drag.current && setDragging(false)
    };

    const showMessage = (text, type, timeout) => {
        setMessage({ show: true, text, type, })
        setTimeout(() =>
            setMessage({ show: false, text: null, type: null, },), timeout);
    };

    return (
        <div
            ref={drop}
            className={classList['FilesDragAndDrop']}
        >
            {message.show && (
                <div
                    className={classNames(
                        classList['FilesDragAndDrop__placeholder'],
                        classList[`FilesDragAndDrop__placeholder--${message.type}`],
                    )}
                >
                    {message.text}
                    <span
                        role='img'
                        aria-label='emoji'
                        className={classList['area__icon']}
                    >
                        {message.type === 'error' ? <>&#128546;</> : <>&#128536;</>}
                    </span>
                </div>
            )}
            {dragging && (
                <div
                    ref={drag}
                    className={classList['FilesDragAndDrop__placeholder']}
                >
                    请放手
                    <span
                        role='img'
                        aria-label='emoji'
                        className={classList['area__icon']}
                    >
                        &#128541;
                    </span>
                </div>
            )}
            {props.children}
        </div>
    );
}

FilesDragAndDrop.propTypes = {
    onUpload: PropTypes.func.isRequired,
    children: PropTypes.node.isRequired,
    count: PropTypes.number,
    formats: PropTypes.arrayOf(PropTypes.string)
}

export { FilesDragAndDrop };

App.js

import React, { Component } from 'react';
import { FilesDragAndDrop } from '../components/Common/FilesDragAndDropHook';
import classList from '../scss/components/Common/FilesDragAndDrop.scss';

export default class App extends Component {
    onUpload = (files) => {
        console.log(files);
    };
    render () {
        return (
            <FilesDragAndDrop
                onUpload={this.onUpload}
                count={1}
                formats={['jpg', 'png', 'gif']}
            >
                <div className={classList['FilesDragAndDrop__area']}>
                    传下文件试试?
            <span
                        role='img'
                        aria-label='emoji'
                        className={classList['area__icon']}
                    >
                        &#128526;
            </span>
                </div>
            </FilesDragAndDrop>
        )
    }
}

FilesDragAndDrop.scss

.FilesDragAndDrop {
  position: relative;

  .FilesDragAndDrop__placeholder {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: 100%;
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-flow: column nowrap;
    background-color: #e7e7e7;
    border-radius: 12px;
    color: #7f8e99;
    font-size: 24px;
    opacity: 1;
    text-align: center;
    line-height: 1.4;

    &.FilesDragAndDrop__placeholder--error {
      background-color: #f7e7e7;
      color: #cf8e99;
    }

    &.FilesDragAndDrop__placeholder--success {
      background-color: #e7f7e7;
      color: #8ecf99;
    }

    .area__icon {
      font-size: 64px;
      margin-top: 20px;
    }
  }
}

.FilesDragAndDrop__area {
  width: 300px;
  height: 200px;
  padding: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-flow: column nowrap;
  font-size: 24px;
  color: #555555;
  border: 2px #c3c3c3 dashed;
  border-radius: 12px;

  .area__icon {
    font-size: 64px;
    margin-top: 20px;
  }
}

然后你就可以拿到文件慢慢耍了。。。

❤️ 前端劝退师公众号交流群

  • 添加微信:huab119,回复:加群。加入前端劝退师公众号交流群。

2019-07-31 23:39:09 weixin_42541867 阅读数 88
  • 《C语言/C++学习指南》Qt界面开发篇视频精讲

    本篇讲解如何使用Qt库进行界面开发,是一个从入门到精通的教程。主要的演示平台为VS2008 + Qt4.7.3,从简单的例子入手,覆盖Qt界面开发的近乎全部技术点。(自定义Widget、对话框、事件处理Singal & Slot、界面线程与工作线程、数据视图ItemView、拖放操作Drag & Drop、界面美化Qt Style等等) 提供全部示例代码的下载。 (前四章免费)

    69539 人正在学习 去看看 邵发

今天与你分享的是 redux 作者 Dan 的另外一个很赞的项目 react-dnd (github 9.6k star),dnd 是 Drag and Drop 的意思,为什么他会开发 react-dnd 这个项目,这个拖放库解决了什么问题,和 html5 原生 Drag Drop API 有什么样的联系与不同,设计有什么独特之处?让我们带着这些问题一起来了解一下 React DnD 吧。

React DnD 是什么?

React DnD是React和Redux核心作者 Dan Abramov创造的一组React 高阶组件,可以在保持组件分离的前提下帮助构建复杂的拖放接口。它非常适合Trello 之类的应用程序,其中拖动在应用程序的不同部分之间传输数据,并且组件会根据拖放事件更改其外观和应用程序状态。

64173-68701495e446104d
image

React DnD 的出发点

现有拖放插件的问题

  • jquery 插件思维模式,直接改变DOM

  • 拖放状态改变的影响不仅限于 CSS 类这种改变,不支持更加自定义

HTML5 拖放API的问题

  • 不支持移动端

  • 拖动预览问题

  • 无法开箱即用

React DnD 的需求

  • 默认使用 HTML5 拖放API,但支持

  • 不直接操作 DOM

  • DOM 和拖放的源和目标解耦

  • 融入HTML5拖放中窃取类型匹配和数据传递的想法

React DnD 的特点

专注拖拽,不提供现成组件

React DnD提供了一组强大的原语,但它不包含任何现成组件,而是采用包裹使用者的组件并注入 props 的方式。 它比jQuery UI等更底层,专注于使拖放交互正确,而把视觉方面的效果例如坐标限制交给使用者处理。这其实是一种关注点分离的原则,例如React DnD不打算提供可排序组件,但是使用者可以基于它快速开发任何需要的自定义的可排序组件。

单向数据流

类似于 React 一样采取声明式渲染,并且像 redux 一样采用单向数据流架构,实际上内部使用了 Redux

隐藏了平台底层API的问题

HTML5拖放API充满了陷阱和浏览器的不一致。 React DnD为您内部处理它们,因此使用者可以专注于开发应用程序而不是解决浏览器问题。

可扩展可测试

React DnD默认提供了HTML5拖放API封装,但它也允许您提供自定义的“后端(backend)”。您可以根据触摸事件,鼠标事件或其他内容创建自定义DnD后端。例如,内置的模拟后端允许您测试Node环境中组件的拖放交互。

为未来做好了准备

React DnD不会导出mixins,并且对任何组件同样有效,无论它们是使用ES6类,createReactClass还是其他React框架创建的。而且API支持了ES7 装饰器。

React DnD 的基本用法

下面是让一个现有的Card组件改造成可以拖动的代码示例:

// Let's make <Card text='Write the docs' /> draggable!
import React, { Component } from 'react';import PropTypes from 'prop-types';import { DragSource } from 'react-dnd';import { ItemTypes } from './Constants';
/** * Implements the drag source contract. */const cardSource = {  beginDrag(props) {    return {      text: props.text    };  }};
/** * Specifies the props to inject into your component. */function collect(connect, monitor) {  return {    connectDragSource: connect.dragSource(),    isDragging: monitor.isDragging()  };}
const propTypes = {  text: PropTypes.string.isRequired,
  // Injected by React DnD:  isDragging: PropTypes.bool.isRequired,  connectDragSource: PropTypes.func.isRequired};
class Card extends Component {  render() {    const { isDragging, connectDragSource, text } = this.props;    return connectDragSource(      <div style={{ opacity: isDragging ? 0.5 : 1 }}>        {text}      </div>    );  }}
Card.propTypes = propTypes;
// Export the wrapped component:export default DragSource(ItemTypes.CARD, cardSource, collect)(Card);

可以看出通过 DragSource 函数可以生成一个高阶组件,包裹 Card 组件之后就可以实现可以拖动。Card组件可以通过 props 获取到 text, isDragging, connectDragSource 这些被 React DnD 注入的 prop,可以根据拖拽状态来自行处理如何显示。

那么 DragSource, connectDragSource, collect, cardSource 这些都是什么呢?下面将会介绍React DnD 的基本概念。

React DnD 的基本概念

Backend

React DnD 抽象了后端的概念,你可以使用 HTML5 拖拽后端,也可以自定义 touch、mouse 事件模拟的后端实现,后端主要用来抹平浏览器差异,处理 DOM 事件,同时把 DOM 事件转换为 React DnD 内部的 redux action。

Item

React DnD 基于数据驱动,当拖放发生时,它用一个数据对象来描述当前的元素,比如{ cardId: 25 }

Type

类型类似于 redux 里面的actions types 枚举常量,定义了应用程序里支持的拖拽类型。

Monitor

拖放操作都是有状态的,React DnD 通过 Monitor 来存储这些状态并且提供查询

Connector

Backend 关注 DOM 事件,组件关注拖放状态,connector 可以连接组件和 Backend ,可以让 Backend 获取到 DOM。

DragSource

将组件使用 DragSource 包裹让它变得可以拖动,DragSource 是一个高阶组件:

DragSource(type, spec, collect)(Component)
  • **type**: 只有 DragSource 注册的类型和DropTarget 注册的类型完全匹配时才可以drop

  • **spec**: 描述DragSource 如何对拖放事件作出反应

    • **beginDrag(props, monitor, component)** 开始拖拽事件

    • **endDrag(props, monitor, component)** 结束拖拽事件

    • **canDrag(props, monitor)** 重载是否可以拖拽的方法

    • **isDragging(props, monitor)** 可以重载是否正在拖拽的方法

  • **collect**: 类似一个map函数用最终inject给组件的对象,这样可以让组件根据当前的状态来处理如何展示,类似于 redux connector 里面的 mapStateToProps ,每个函数都会接收到 connectmonitor 两个参数,connect 是用来和 DnD 后端联系的, monitor是用来查询拖拽状态信息。

DropTarget

将组件使用 DropTarget 包裹让它变得可以响应 drop,DropTarget 是一个高阶组件:

DropTarget(type, spec, collect)(Component)
  • **type**: 只有 DropTarget 注册的类型和DragSource 注册的类型完全匹配时才可以drop

  • **spec**: 描述DropTarget 如何对拖放事件作出反应

    • **drop(props, monitor, component)** drop 事件,返回值可以让DragSource endDrag 事件内通过monitor获取。

    • **hover(props, monitor, component)** hover 事件

    • **canDrop(props, monitor)** 重载是否可以 drop 的方法

DragDropContext

包裹根组件,可以定义backend,DropTargetDropTarget 包装过的组件必须在 DragDropContext 包裹的组件内

DragDropContext(backend)(RootComponent)

React DnD 核心实现

64173-31c078c2c0a276ae.png
image.png

<input type="file" accept=".jpg, .jpeg, .png, .gif" style="display: none;">

dnd-core

核心层主要用来实现拖放原语

  • 实现了拖放管理器,定义了拖放的交互

  • 和框架无关,你可以基于它结合 react、jquery、RN等技术开发

  • 内部依赖了 redux 来管理状态

  • 实现了 DragDropManager,连接 BackendMonitor

  • 实现了 DragDropMonitor,从 store 获取状态,同时根据store的状态和自定义的状态获取函数来计算最终的状态

  • 实现了 HandlerRegistry 维护所有的 types

  • 定义了 Backend , DropTarget , DragSource 等接口

  • 工厂函数 createDragDropManager 用来接收传入的 backend 来创建一个管理器

export function createDragDropManager<C>(   backend: BackendFactory,    context: C,): DragDropManager<C> {  return new DragDropManagerImpl(backend, context)}

react-dnd

上层 React 版本的Drag and Drop的实现

  • 定义 DragSource, DropTarget, DragDropContext 等高阶组件

  • 通过业务层获取 backend 实现和组件来给核心层工厂函数

  • 通过核心层获取状态传递给业务层

DragDropContext 从业务层接受 backendFactory 和 backendContext 传入核心层 createDragDropManager 创建 DragDropManager 实例,并通过 Provide 机制注入到被包装的根组件。


/** * Wrap the root component of your application with DragDropContext decorator to set up React DnD. * This lets you specify the backend, and sets up the shared DnD state behind the scenes. * @param backendFactory The DnD backend factory * @param backendContext The backend context */export function DragDropContext(   backendFactory: BackendFactory, backendContext?: any,) {    // ...  return function decorateContext<        TargetClass extends         | React.ComponentClass<any>         | React.StatelessComponent<any> >(DecoratedComponent: TargetClass): TargetClass & ContextComponent<any> {       const Decorated = DecoratedComponent as any     const displayName = Decorated.displayName || Decorated.name || 'Component'
        class DragDropContextContainer extends React.Component<any>         implements ContextComponent<any> {          public static DecoratedComponent = DecoratedComponent           public static displayName = `DragDropContext(${displayName})`
            private ref: React.RefObject<any> = React.createRef()
            public render() {               return (                   // 通过 Provider 注入 dragDropManager                    <Provider value={childContext}>                     <Decorated                          {...this.props}                         ref={isClassComponent(Decorated) ? this.ref : undefined}                        />                  </Provider>             )           }       }
        return hoistStatics(            DragDropContextContainer,           DecoratedComponent,     ) as TargetClass & DragDropContextContainer }}

那么 Provider 注入的 dragDropManager 是如何传递到DragDropContext 内部的 DragSource 等高阶组件的呢?

请看内部 decorateHandler 的实现

export default function decorateHandler<Props, TargetClass, ItemIdType>({   DecoratedComponent, createHandler,  createMonitor,  createConnector,    registerHandler,    containerDisplayName,   getType,    collect,    options,}: DecorateHandlerArgs<Props, ItemIdType>): TargetClass &   DndComponentClass<Props> {
    //  class DragDropContainer extends React.Component<Props>      implements DndComponent<Props> {
            public receiveType(type: any) {         if (!this.handlerMonitor || !this.manager || !this.handlerConnector) {              return          }
            if (type === this.currentType) {                return          }
            this.currentType = type
            const { handlerId, unregister } = registerHandler(              type,               this.handler,               this.manager,           )
            this.handlerId = handlerId          this.handlerMonitor.receiveHandlerId(handlerId)         this.handlerConnector.receiveHandlerId(handlerId)
            const globalMonitor = this.manager.getMonitor()         const unsubscribe = globalMonitor.subscribeToStateChange(               this.handleChange,              { handlerIds: [handlerId] },            )
            this.disposable.setDisposable(              new CompositeDisposable(                    new Disposable(unsubscribe),                    new Disposable(unregister),             ),          )       }

        public getCurrentState() {          if (!this.handlerConnector) {               return {}           }           const nextState = collect(              this.handlerConnector.hooks,                this.handlerMonitor,            )
            return nextState        }
        public render() {           return (        // 使用 consume 获取 dragDropManager 并传递给 receiveDragDropManager                <Consumer>                  {({ dragDropManager }) => {                     if (dragDropManager === undefined) {                            return null                     }                       this.receiveDragDropManager(dragDropManager)
                        // Let componentDidMount fire to initialize the collected state                     if (!this.isCurrentlyMounted) {                         return null                     }
                        return (              // 包裹的组件                          <Decorated                              {...this.props}                             {...this.state}                             ref={                                   this.handler && isClassComponent(Decorated)                                     ? this.handler.ref                                      : undefined                             }                           />                      )                   }}              </Consumer>         )       }
    // receiveDragDropManager 将 dragDropManager 保存在 this.manager 上,并通过 dragDropManager 创建 monitor,connector     private receiveDragDropManager(dragDropManager: DragDropManager<any>) {         if (this.manager !== undefined) {               return          }           this.manager = dragDropManager
            this.handlerMonitor = createMonitor(dragDropManager)            this.handlerConnector = createConnector(dragDropManager.getBackend())           this.handler = createHandler(this.handlerMonitor)       }   }
    return hoistStatics(DragDropContainer, DecoratedComponent) as TargetClass &     DndComponentClass<Props>}

DragSource 使用了 decorateHandler 高阶组件,传入了createHandler, registerHandler, createMonitor, createConnector 等函数,通过 Consumer 拿到 manager 实例,并保存在 this.manager,并将 manager 传给前面的函数生成 handlerMonitor, handlerConnector, handler

/** * Decorates a component as a dragsource * @param type The dragsource type * @param spec The drag source specification * @param collect The props collector function * @param options DnD optinos */export default function DragSource<Props, CollectedProps = {}, DragObject = {}>( type: SourceType | ((props: Props) => SourceType),  spec: DragSourceSpec<Props, DragObject>,    collect: DragSourceCollector<CollectedProps>,   options: DndOptions<Props> = {},) {   // ...    return function decorateSource<     TargetClass extends         | React.ComponentClass<Props>           | React.StatelessComponent<Props>   >(DecoratedComponent: TargetClass): TargetClass & DndComponentClass<Props> {        return decorateHandler<Props, TargetClass, SourceType>({            containerDisplayName: 'DragSource',         createHandler: createSource,            registerHandler: registerSource,            createMonitor: createSourceMonitor,         createConnector: createSourceConnector,         DecoratedComponent,         getType,            collect,            options,        })  }}

比如传入的 DragSource 传入的 createHandler函数的实现是 createSourceFactory,可以看到


export interface Source extends DragSource {    receiveProps(props: any): void}
export default function createSourceFactory<Props, DragObject = {}>(    spec: DragSourceSpec<Props, DragObject>,) {  // 这里实现了 Source 接口,而 Source 接口是继承的 dnd-core 的 DragSource   class SourceImpl implements Source {        private props: Props | null = null      private ref: React.RefObject<any> = createRef()
        constructor(private monitor: DragSourceMonitor) {           this.beginDrag = this.beginDrag.bind(this)      }
        public receiveProps(props: any) {           this.props = props      }
    // 在 canDrag 中会调用通过 spec 传入的 canDrag 方法     public canDrag() {          if (!this.props) {              return false            }           if (!spec.canDrag) {                return true         }
            return spec.canDrag(this.props, this.monitor)       }    // ... }
    return function createSource(monitor: DragSourceMonitor) {      return new SourceImpl(monitor) as Source    }}

react-dnd-html5-backend

react-dnd-html5-backend 是官方的html5 backend 实现

主要暴露了一个工厂函数,传入 manager 来获取 HTML5Backend 实例

export default function createHTML5Backend(manager: DragDropManager<any>) { return new HTML5Backend(manager)}

HTML5Backend 实现了 Backend 接口

interface Backend { setup(): void   teardown(): void    connectDragSource(sourceId: any, node?: any, options?: any): Unsubscribe    connectDragPreview(sourceId: any, node?: any, options?: any): Unsubscribe   connectDropTarget(targetId: any, node?: any, options?: any): Unsubscribe}
export default class HTML5Backend implements Backend {  // DragDropContxt node 节点 或者 window  public get window() {      if (this.context && this.context.window) {          return this.context.window      } else if (typeof window !== 'undefined') {         return window       }       return undefined    }
    public setup() {        if (this.window === undefined) {            return      }
        if (this.window.__isReactDndBackendSetUp) {         throw new Error('Cannot have two HTML5 backends at the same time.')     }       this.window.__isReactDndBackendSetUp = true     this.addEventListeners(this.window) }
    public teardown() {     if (this.window === undefined) {            return      }
        this.window.__isReactDndBackendSetUp = false        this.removeEventListeners(this.window)      this.clearCurrentDragSourceNode()       if (this.asyncEndDragFrameId) {         this.window.cancelAnimationFrame(this.asyncEndDragFrameId)      }   }
  // 在 DragSource 的node节点上绑定事件,事件处理器里会调用action  public connectDragSource(sourceId: string, node: any, options: any) {       this.sourceNodes.set(sourceId, node)        this.sourceNodeOptions.set(sourceId, options)
        const handleDragStart = (e: any) => this.handleDragStart(e, sourceId)       const handleSelectStart = (e: any) => this.handleSelectStart(e)
        node.setAttribute('draggable', true)        node.addEventListener('dragstart', handleDragStart)     node.addEventListener('selectstart', handleSelectStart)
        return () => {          this.sourceNodes.delete(sourceId)           this.sourceNodeOptions.delete(sourceId)
            node.removeEventListener('dragstart', handleDragStart)          node.removeEventListener('selectstart', handleSelectStart)          node.setAttribute('draggable', false)       }   }}

React DnD 设计中犯过的错误

  • 使用了 mixin

    • 破坏组合

    • 应使用高阶组件

  • 核心没有 react 分离

  • 潜逃放置目标的支持

  • 镜像源

参考资料

没有更多推荐了,返回首页