在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的更多相关文章

  1. React Native 之 Touchable 介绍与使用

    前言 学习本系列内容需要具备一定 HTML 开发基础,没有基础的朋友可以先转至 HTML快速入门(一) 学习 本人接触 React Native 时间并不是特别长,所以对其中的内容和性质了解可能会有所 ...

  2. 利用React/anu编写一个弹出层

    本文将一步步介绍如何使用React或anu创建 一个弹出层. React时代,代码都是要经过编译的,我们很多时间都耗在babel与webpack上.因此本文也介绍如何玩webpack与babel. 我 ...

  3. react native 触摸Touchable***的区别(TouchableWithoutFeedback、TouchableOpacity、TouchableHighlight、TouchableNativeFeedback)

    一.问题背景: react native的跨平台开发没有button的概念,而是使用touchable系列实现点击触发效果. 而touchable系列就有四个之多,而且相互之间仍有较大差别,这就给我们 ...

  4. React/anu实现弹出层2

    这次是使用了一个比较罕见的APIReactDOM.unstable_renderSubtreeIntoContainer,ReactDOM.unstable_renderSubtreeIntoCont ...

  5. React Native中Touchable组件的使用

    截图如下: /** * Sample React Native App * https://github.com/facebook/react-native * @flow */ import Rea ...

  6. React Native交互组件之Touchable

    React Native交互组件之Touchable:只要在组件外面包一个Touchable组件就可以实现点击交互. TouchableHighlight:高亮触摸 当点击时,组件的透明度会改变,可以 ...

  7. 发布高性能迷你React框架anu

    anu, 读作[安努],原意为苏美尔的主神. anu是我继avalon之后又一个新框架(github仓库为https://github.com/RubyLouvre/anu, 欢迎加星与试用) 此框架 ...

  8. 高性能迷你React框架anu在低版本IE的实践

    理想是丰满的,现实是骨感的,react早期的版本虽然号称支持IE8,但是页面总会不自觉切换到奇异模式下,导致报错.因此必须让react连IE6,7都支持,这才是最安全.但React本身并不支持IE6, ...

  9. anu - react

    import { options } from "./util"; import { Children } from "./Children"; import ...

随机推荐

  1. C/C++ 与 Python 的通信

    作者:Jerry Jho链接:https://www.zhihu.com/question/23003213/answer/56121859来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商 ...

  2. sshd服务安装

    SSHD服务 介绍:SSH 协议:安全外壳协议.为 Secure Shell 的缩写.SSH 为建立在应用层和传输层基础上的安全协议. 作用:sshd服务使用SSH协议可以用来进行远程控制, 或在计算 ...

  3. solr 使用edismax来控制评分

    如何控制评分 如果设置了sort字段,那么将会按照sort字段的顺序返回结果. 如果没有设置sort字段,那么将会根据相关度打分来排序.也就是说,相关度更高的排在前面. 如何来定制适合自身业务的排序打 ...

  4. Linux交换空间(swap space)

    每次安装Linux的时候,都会要求配置交换分区,那么这个分区是干嘛的呢?不设置这个分区有什么后果?如果一定要设置,设置多大比较合适?本篇将试图回答这些问题并尽量覆盖所有swap相关的知识. 下面的所有 ...

  5. 人工智能时代,是时候学点Python了!

    “是时候学点Python了”.作为一名不怎么安分的程序员,你或许觉得,产生这样的想法并不奇怪,但学习Python却是出于自己对工作现状以及如何应对未来挑战所作出的思考.读过我以前博客的朋友,可能都知道 ...

  6. javascript继承之学习笔记

    今天记录一下学习javascript的继承. 继承基本上是基于“类”来说的,而javascript中并不存在真正的类,所以就出现了各种模拟“类”的行为,然后就堂而皇之的使用起了类的概念.这里不谈“类” ...

  7. failed to open stream: Permission denied in警告错误

    问题是文件所在目录的权限问题导致的.只需要将警告文件所在的目录权限更改为777(至少是006)即可 例如 (...a.log)failed to open stream: Permission den ...

  8. ubantu 操作

    应用程序启动器“monodevelop.desktop”还没有被标记为可信任的. 于是在网上查询解决方案,在ubuntu中文论坛找到一个帖子提到这个问题的解决方法,尝试之解决. 帖子地址:http:/ ...

  9. apache(httpd)配置

    1.简单配置 1 监听地址 2 主页目录 3 别名 4 目录访问的身份验证 5 https 6 MPM(under linux) * 配置文件中路径.文件名均不支持中文. <<<&l ...

  10. Laravel-初体验笔记

    一直想学Laravel却动不了手,刚好需要研究一个workflow之类的功能,有个Laravel项目一个登陆就把我搞晕,看Laravel文档看的也不能看进去,直接新建个Laravel仿一个,动手搞起来 ...