React/anu实现Touchable

react

在RN中有一个叫Touchable 的组件,这里我们重演如何实现它。

Touchable存在的意义是屏蔽click的问题。移动端与手机的click 在一些浏览器是有差异,比如说著名的300ms延迟。

Touchable的实现要点是将事件通过包装,然后绑定在它的下一级元素节点上。

而一级元素节点可以通过this.props.children[0]取到。为了解决兼容问题,我们通常用React.Children.only(this.props.children)来取这个节点。

而事件的传递则通过React.cloneElement(child, newPropsWithEvents)实现。

最后是事件包装,在移动端下,点击事件是通过4个事件实现的:ontouchstart, ontouchmove, ontouchend, ontouchcancel

为了模块化的需要,我们将事件包装这块拆出来,叫gesture.js

/**

* touchable手势处理,解决Scroller内部的手势冲突

* 在滚动时不会触发active

* 在active之后发生滚动会取消active状态

*/

import ReactDOM from 'react-dom';

const TAP_SLOP = 5;

export const TAP_DELAY = 50;

/**

* @param endPoint

* @param startPoint

* @returns {number}

* 求两个点之间的距离

*/

function getDistance(endPoint, startPoint) {

return Math.sqrt(Math.pow(endPoint.pageX - startPoint.pageX, 2) + Math.pow(endPoint.pageY - startPoint.pageY, 2));

}

/**

* @param endPoint

* @param startPoint

* @returns {boolean}

*如果移动的距离太远了,应该是认为其他事件,而不是Tap事件

*/

function onTouchMoveShouldCancelTap(endPoint, startPoint) {

return getDistance(endPoint, startPoint) > TAP_SLOP;

}

/**

* @param evt

* @returns {touch/null}

* 获取触点

*/

function getTouchPoint(evt) {

return evt.touches.length ? { pageX: evt.touches[0].pageX, pageY: evt.touches[0].pageY } : null;

}

/**

* @param domNode

* @param activeClass

* 移除item的activeClass

*/

function removeActiveClass(domNode, activeClass) {

if (domNode && activeClass) {

domNode.className = domNode.className.replace(` ${activeClass}`, '');

}

}

/**

* @param scroller

* @returns {boolean}

* 判断组件是否在滚动

*/

function isScrolling(scroller) {

return scroller ? scroller.isScrolling : false;

}

function isAnySwipeMenuOpen(swipeMenuList) {

return swipeMenuList ? swipeMenuList.openIndex !== -1 : false;

}

// touchStart的位置,是否需要放弃Tap触发,Tap周期(start,move,end)是否已经结束

let startPoint,

shouldAbortTap;

let captured = null;

export default function ({

component,

scroller,

swipeMenuList,

activeClass,

onTap,

onTouchStart,

disabled

}) {

const gestureObj = {

onTouchStart(evt) {

const domNode = ReactDOM.findDOMNode(component);

removeActiveClass(domNode, activeClass);

// 如果组件正在滚动,直接放弃Tap触发

shouldAbortTap = isScrolling(scroller) || isAnySwipeMenuOpen(swipeMenuList);

startPoint = getTouchPoint(evt);

onTouchStart(evt);

if (!captured) {

captured = domNode;

}

// TAP_DELAY之后再次判断是否要触发Tap,如果这段时间内出现了大的位移,if后面的逻辑就不会执行

setTimeout(() => {

const className = activeClass;

if (!shouldAbortTap && className && captured === domNode && !disabled) {

domNode.className += ` ${className}`;

}

}, TAP_DELAY);

},

onTouchMove(evt) {

const domNode = ReactDOM.findDOMNode(component);

const currentPoint = getTouchPoint(evt);

// 根据touchmove的距离判断是否要放弃tap

if (onTouchMoveShouldCancelTap(currentPoint, startPoint)) {

shouldAbortTap = true;

captured = null;

removeActiveClass(domNode, activeClass);

}

},

onTouchEnd(evt) {

const target = evt.target;

const domNode = ReactDOM.findDOMNode(component);

// 如果需要触发tap,在TAP_DELAY之后触发onTap回调

if (!shouldAbortTap && captured === domNode) {

setTimeout(() => {

if (!disabled) {

onTap(target);

}

removeActiveClass(domNode, activeClass);

captured = null;

}, TAP_DELAY + 10);

} else if (shouldAbortTap) {

captured = null;

}

},

onTouchCancel() {

const domNode = ReactDOM.findDOMNode(component);

removeActiveClass(domNode, activeClass);

}

};

return gestureObj;

}

Touchable.js的源码如下

import { Component, PropTypes,cloneElement } from 'react';

import gesture from './gesture';

export class Touchable extends Component {

static propTypes = {

/**

* @property touchClass

* @type String

* @default null

* @description 触摸Touchable时附加的className,可以用来实现Native常见的触摸反馈功能(例如给触摸区域添加深色背景或者改变透明度等等)。

*/

touchClass: PropTypes.string,

/**

* @property onTap

* @type Function

* @default null

* @param {DOMElement} target tap事件的target

* @description 给Touchable绑定的onTap事件。

*/

onTap: PropTypes.func,

/**

* @property disabled

* @type Bool

* @default false

* @description Touchable是否处于可点击状态,如果设为true,那么onTap事件回调和触摸反馈效果都不可用。

* @version 3.0.7

*/

disabled: PropTypes.bool,

/**

* @skip 给List定制的属性

*/

onTouchStart: PropTypes.func,

/**

* @skip 内部使用标志

*/

internalUse: PropTypes.bool,

children: PropTypes.object

};

static defaultProps = {

onTouchStart: () => {

},

touchClass: null,

onTap: () => {

},

internalUse: false,

disabled: false

};

static contextTypes = {

scroller: PropTypes.object,

swipeMenuList: PropTypes.object

};

render() {

if (process.env.NODE_ENV !== 'production') {

if (this.props.touchClass == null && !this.props.internalUse) {

console.error('yo-touchable: Touchable组件没有设置touchClass, 出于用户体验考虑, 应该尽量给触摸区域添加触摸反馈。');

}

}

const onlyChild = React.Children.only(this.props.children);

const gestureObj = gesture({

component: this,

scroller: this.context.scroller,

swipeMenuList: this.context.swipeMenuList,

activeClass: this.props.touchClass,

onTap: this.props.onTap,

onTouchStart: this.props.onTouchStart,

disabled: this.props.disabled

});

const { onTouchStart, onTouchMove, onTouchEnd, onTouchCancel } = gestureObj;

return cloneElement(onlyChild, { onTouchStart, onTouchMove, onTouchEnd, onTouchCancel });

}

}

Touchable就是将用户传人它的属性提出来,复制到第一个子节点的props上。这个过程我们用cloneElement实现。

使用

<Touchable onTap={(e)=>{ console.log(e)}}

以上是 React/anu实现Touchable 的全部内容, 来源链接: utcz.com/z/383726.html

回到顶部