news 2026/11/5 8:19:52

react-native-gesture-handler 的 Gesture 对象:手势创建、组合与 v3 Hook API 迁移指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
react-native-gesture-handler 的 Gesture 对象:手势创建、组合与 v3 Hook API 迁移指南
  • 移动开发
  • UI组件

【免费下载链接】react-native-gesture-handler

Declarative API exposing platform native touch and gesture system to React Native.

项目地址:https://gitcode.com/gh_mirrors/re/react-native-gesture-handler
点击查看免费下载

Gesture是 react-native-gesture-handler 中用于创建与组合手势的核心对象:通过Gesture.Tap()、Gesture.Pan()等工厂方法生成具体手势实例,再通过Race、Simultaneous、Exclusive将其组合成复杂交互,最后交给GestureDetector挂载到视图上。本文以仓库中 version-2.x 版本文档的 gesture.md 为主体,结合 手势源码 与其 v3 Hook API 实现,完整梳理Gesture的 API 参考、组合语义、底层工作方式以及向新 API 迁移的路径,帮助你理解并准确使用这套声明式手势系统。

Gesture 是什么:声明式手势的入口对象

在 RNGH 2.x 的 API 中,Gesture是一个抽象对象,它封装了"创建手势"与"组合手势"两件事。正如文档开篇所述:Gestureis the object that allows you to create and compose gestures(Gesture是允许你创建和组合手势的对象)。

从源码看,gesture.ts 中定义了抽象类Gesture,它要求每个具体手势实现三个核心方法:

  • toGestureArray():返回手势数组,无论手势是用哪种对象创建的,都提供统一的接口供创建与更新 handler 使用;
  • initialize():为手势实例分配handlerTag,并在设置了 ref 时同步ref.current;
  • prepare():确保定义手势关系的属性值是数组,并做必要的预处理,用于在原生侧更新 handler 之前配置手势间的关系。

在此基础上,BaseGesture是所有具体手势的基类。构造函数中通过nextGestureId++为每个手势分配唯一的gestureId,正如源码注释所述:由于每个配置都有唯一 id,当useMemo的依赖不变时配置不会重建、id 保持不变;一旦 id 改变,就意味着手势配置已更新、需要同步到原生侧。

工厂方法全部集中在 gestureObjects.ts 的GestureObjects对象中,每个方法返回对应手势类的实例(如Tap: () => new TapGesture())。

基础手势工厂方法:一次创建一个手势

Gesture对象提供了若干无参工厂方法,每个都"以默认配置、无回调"的方式创建一个全新的手势实例:

工厂方法返回的手势类型手势类别说明
Gesture.Tap()TapGesture离散型识别一次或多次点击,详见 tap-gesture.md
Gesture.Pan()PanGesture连续型识别拖拽/平移并跟踪其位移
Gesture.LongPress()LongPressGesture离散型在视图上按压足够长时间后激活
Gesture.Fling()FlingGesture离散型在移动足够快时激活
Gesture.Pinch()PinchGesture连续型跟踪两指间距离,用于缩放
Gesture.Rotation()RotationGesture连续型识别旋转并跟踪其角度
Gesture.Hover()HoverGesture连续型识别鼠标/触控笔在视图上的悬停
Gesture.ForceTouch()ForceTouchGesture连续型(仅 iOS)识别触摸压力
Gesture.Manual()ManualGesture手动控制型无特定激活条件,需用状态管理器手动控制
Gesture.Native()NativeGesture原生组件型让其他触摸处理组件参与 RNGH 手势系统

其中最典型的使用方式如下:

import { GestureDetector, Gesture } from 'react-native-gesture-handler'; function App() { const tap = Gesture.Tap(); return ( <GestureDetector gesture={tap}> <Animated.View /> </GestureDetector> ); }

工厂方法创建的手势默认没有回调,需要继续链式调用onBegin、onStart、onUpdate等方法来配置行为。以 tapGesture.ts 为例,TapGesture的构造函数会自动调用shouldCancelWhenOutside(true),并允许通过minPointers、numberOfTaps、maxDistance、maxDuration、maxDelay、maxDeltaX、maxDeltaY等链式方法配置点击语义(例如numberOfTaps(2)即双击)。而 panGesture.ts 中的PanGesture则提供了activeOffsetX/Y、failOffsetX/Y、minDistance、minVelocity、averageTouches(Android)、enableTrackpadTwoFingerGesture(iOS)等连续型手势专属配置,并覆写了onChange以注入changeEventCalculator,从而在回调中额外提供changeX/changeY增量数据。

所有具体手势类最终都会注册对应的原生 handler 名称(例如TapGesture的handlerName为'TapGestureHandler'),这说明Gesture对象是 RNGH 原生手势系统在 JS 侧的声明式封装。

组合手势:让多个手势协同工作

除了创建单个手势,Gesture还提供了三种组合方法,用于在同一组件上表达多个手势之间的竞争或协作关系。它们都返回ComposedGesture类型,且接受可变数量的手势参数。

Gesture.Race(gesture1, gesture2, gesture3, ...)

由传入参数组成的手势。其中只有一个可以激活,且对手势的激活没有任何限制——最先激活的那个会取消其余所有手势。它等价于"挂载多个手势 handler 但彼此不定义simultaneousHandlers/waitFor关系"。

典型场景:组件既可拖拽又支持长按弹出选项,但长按激活后不希望组件再被拖动。此时用Gesture.Race(dragGesture, longPressGesture)即可让两者竞争,只有先激活的一方胜出,完整示例见 gesture-composition.md 的 Race 章节。

Gesture.Simultaneous(gesture1, gesture2, gesture3, ...)

由传入参数组成的手势。所有手势都可以同时激活,互不取消。它等价于为每个 handler 相互设置simultaneousHandlers属性。

典型场景:图片查看器中同时进行缩放、旋转与平移,示例:

const composed = Gesture.Simultaneous( dragGesture, Gesture.Simultaneous(zoomGesture, rotateGesture) );

Gesture.Exclusive(gesture1, gesture2, gesture3, ...)

由传入参数组成的手势。只有一个可以激活,且第一个手势优先级最高、第二个次之、依次递减。当所有手势都处于BEGAN状态、第二个手势的激活条件已满足时,它不会立即激活,而是等待第一个手势失败后才激活(若第一个手势激活则第二个被取消)。

它特别适合组合激活条件相似的手势,例如在同一组件上同时响应单击与双击:若不使用Exclusive,单次点击时单击手势每次都会激活并取消双击手势;正确写法是Gesture.Exclusive(doubleTap, singleTap),让单击等待双击失败。

从源码看,gestureComposition.ts 中ComposedGesture的prepare()会遍历子手势并为其扩展simultaneousWith与requireToFail关系;SimultaneousGesture会为每个手势构造"除自身外其余所有子手势"的数组作为并行关系;ExclusiveGesture则让每个手势组等待它之前的所有手势组失败(requireToFail依次累积)。也就是说,三种组合在底层最终都转化为手势之间的simultaneousWith/requireToFail关系,与旧 API 的 ref 式配置在语义上等价。

跨组件手势关系:三种组合方法的补充

组合方法要求所有手势挂载在同一个GestureDetector下。若需要跨组件协调手势,BaseGesture还提供三个链式方法(定义见 gesture.ts 的addDependency相关实现):

  • simultaneousWithExternalGesture(...gestures):允许不同组件上的手势同时被识别;
  • requireExternalGestureToFail(...gestures):延迟本手势的激活,直到传入的所有手势失败(或从未开始);
  • blocksExternalGesture(...gestures):与requireExternalGestureToFail方向相反,常用于让ScrollView等待其下所有手势(如列表项内的缩放),即"许多手势阻塞一个手势"。

这三种方法分别对应config中的simultaneousWith、requireToFail、blocksHandlers三个关系字段,是跨组件场景下组合语义的延伸,具体示例同样见 gesture-composition.md。

基础配置方法:每个手势通用的链式选项

除具体手势类各自的专属配置外,BaseGesture还提供了一套所有手势通用的链式配置方法:

方法作用平台/默认值
enabled(boolean)是否分析触摸事件流默认开启
shouldCancelWhenOutside(boolean)手指离开视图区域时取消或失败识别默认false(Tap 默认true)
hitSlop(HitSlop)控制视图区域内可用于开始识别手势的部分;传负数会按点数均匀收窄各边—
activeCursor(cursor)手势激活时使用的 CSS cursor仅 Web,默认"auto"
mouseButton(button)响应哪个鼠标按键,可用\|组合如MouseButton.LEFT \| MouseButton.RIGHT仅 Web 与 Android,默认LEFT
runOnJS(boolean)为true时所有回调强制在 JS 线程执行默认false
withRef(ref)将手势对象写入 ref,用于与旧 API 互操作—
withTestId(id)设置 testID,便于测试中查询—
cancelsTouchesInView(boolean)激活时是否取消所附着原生 UI 组件(UIButton、UISwitch等)的触摸仅 iOS,默认true

回调方法方面,所有手势共享onBegin(进入BEGAN状态)、onStart(识别成功进入ACTIVE)、onEnd(结束且此前处于ACTIVE,带success参数)、onFinalize(无论识别成功或失败均会调用)、以及onTouchesDown/Move/Up/Cancelled四个底层触摸事件回调(设置它们会自动令config.needsPointerData = true)。连续型手势(ContinousBaseGesture)额外提供onUpdate与onChange(携带相对上一次事件的增量数据),并支持manualActivation(为true时手势不会自行激活,需借助状态管理器手动控制)。

useMemo 的必要性:减少底层更新开销

文档特别强调:考虑用useMemo包裹手势配置,因为它能减少 Gesture Handler 在更新手势时的底层工作量。原因在源码中非常清晰——BaseGesture构造函数为每个手势分配了唯一的gestureId,当useMemo的依赖不变时,手势配置不会重建、id 保持不变,底层无需重复创建或更新 handler。

import React from 'react'; function App() { const gesture = React.useMemo( () => Gesture.Tap().onStart(() => { console.log('Number of taps:', tapNumber + 1); setTapNumber((value) => value + 1); }), [tapNumber, setTapNumber] ); // ... }

这一建议在 GestureDetector 源码 中同样得到印证:GestureDetector通过useMemo(() => gestureConfig.toGestureArray(), [gestureConfig])缓存待挂载的手势数组,并在注释中明确"手势配置应使用 useMemo 包裹以防止不必要的重渲染"。

与 GestureDetector 的配合:挂载与更新

创建并配置好的手势必须交给GestureDetector(gesture属性)才能附着到视图上。GestureDetector的gesture属性可以接收任意基础手势或任意ComposedGesture(Race/Simultaneous/Exclusive),它负责基于手势配置创建和更新原生手势 handler——这也是它区别于旧手势 handler 组件的关键:借助手势组合,单个GestureDetector可以同时识别多个手势。

使用时有两点需要注意(详见 gesture-detector.md):

  1. GestureDetector会使用子树中的第一个原生视图来识别手势,但若该视图仅用于分组子元素,可能被自动折叠(collapsable),导致手势失效;
  2. 同一个手势实例不能复用在多个GestureDetector中,否则会抛出错误。

此外,GestureDetector还提供三个 Web 专属属性:userSelect(可选"none" | "auto" | "text",默认"none")、touchAction(支持全部 CSStouch-action值,默认"none")、enableContextMenu(右键上下文菜单,默认false)。在源码中,这些属性会通过propagateDetectorConfig逐一传播到手势数组里每个基础手势的 config 上。

向前看:向 v3 Hook API 迁移

当前仓库中,Gesture构建器 API 已被标记为弃用:在 gestureObjects.ts 中,每个工厂方法与组合方法都带有@deprecated注释,指向对应的新 Hook API(如useTapGesture、usePanGesture、useCompetingGestures等)。

新的 v3 Hook API 位于 v3/hooks 目录,例如:

  • useTapGesture.ts 取代Gesture.Tap(),通过TapPropsMapping将公开属性名映射为内部配置名(如maxDistance→maxDist、maxDuration→maxDurationMs、maxDelay→maxDelayMs),并默认设置shouldCancelWhenOutside = true,与旧TapGesture构造函数行为一致;
  • useCompetingGestures.ts 取代Gesture.Race(),内部调用useComposedGesture(ComposedGestureName.Race, ...gestures);
  • 同目录下的useSimultaneousGestures与useExclusiveGestures分别对应Gesture.Simultaneous()与Gesture.Exclusive()。

因此,从 v2 迁移到 v3 时,核心心智模型不变:先创建单个手势,再组合成复合手势,最后挂载到检测器上;变化的只是创建方式从"对象工厂方法"变为"Hook 函数"。

小结

Gesture对象是 RNGH 2.x 声明式手势体系的中枢:Gesture.Tap()等工厂方法创建基础手势,Race/Simultaneous/Exclusive在同一组件内组合手势,simultaneousWithExternalGesture等三个方法跨组件建立关系,最终统一交由GestureDetector挂载。理解其gestureId机制与useMemo的配合、组合手势底层转化为simultaneousWith/requireToFail关系的实现方式,是写出高性能、行为可预期的手势代码的关键。面对正在进行的 v3 Hook API 迁移,这套组合与挂载的心智模型依然适用,可以平滑过渡。

  • 移动开发
  • UI组件

【免费下载链接】react-native-gesture-handler

Declarative API exposing platform native touch and gesture system to React Native.

项目地址:https://gitcode.com/gh_mirrors/re/react-native-gesture-handler
点击查看免费下载
上一篇:告别杂乱排版:hexo-theme-fluid 置顶功能让优质文章脱颖而出
下一篇:从0到1理解渲染管线:gh_mirrors/vul/vulkan中ShaderModule与Pipeline创建详解

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/5 8:19:47

NB-IoT接入OneNET实战:智能井盖项目从配置到部署完整指南

去年接了一个智能井盖监测的项目&#xff0c;现场条件直接把WiFi方案判了死刑。地下井室里没有网线、没有供电、更没有WiFi信号&#xff0c;金属井盖盖上之后普通无线基本就失联了。最后定下来的方案就是NB-IoT加移动OneNET平台&#xff0c;这也是我现在做这类电池供电的分散式…

作者头像 李华
网站建设 2026/11/5 8:19:46

Java ERP源码二次开发实战:从环境搭建到权限事务避坑指南

简介&#xff1a;这是一套基于Java开发的企业级ERP系统完整源码&#xff0c;面向有一定Java Web基础、希望深入理解企业级管理系统架构的开发者与学习者&#xff0c;可用于二次开发、课程设计或技术选型参考。压缩包共收录2000个文件&#xff0c;整体约53.27MB&#xff0c;其中…

作者头像 李华
网站建设 2026/11/5 8:19:47

pdf图片转word用什么软件?新手零踩坑实用工具合集

日常办公、学生写论文、整理资料时&#xff0c;很多人都会遇到一个难题&#xff1a;拿到的PDF是扫描图片格式&#xff0c;复制不了文字&#xff0c;无法编辑修改。普通的PDF转换工具完全没用&#xff0c;想要把图片PDF转换成可编辑的Word文档&#xff0c;核心关键就是自带OCR文…

作者头像 李华
网站建设 2026/11/1 12:28:47

整数划分的三种解法:递归、动态规划与回溯实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/4 21:28:49

PDN阻抗优化与芯片供电仿真:Sigrity+ADS双工具实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华