Skip to content

React Native 中 Touchable* 组件事件捕获问题

王大根 edited this page Aug 28, 2019 · 5 revisions

RN 中 Touchable* 组件内部元素的触摸事件会被 Touchable* 组件捕获。这个是符合预期的。但是如果 Touchable* 组件内部嵌套一个 ScrollView 组件,会导致 ScrollView 组件滚动异常(卡顿)。

解决

这个问题最初是同事在 rnx-ui/Overlay 组件的使用过程中发现的(已在 rnx-ui@0.19.3 版本中修复)。下面就结合 Overlay 组件介绍下该问题的3个解法。

最初 Overlay 组件结构如下:

<TouchableWithoutFeedback>
  <View>
    { this.props.children }  
  </View>
</TouchableWithoutFeedback>

实际使用产生结构如下:

<TouchableWithoutFeedback>
  <View>
    <ScrollView>
      { list }  
    </ScrollView>
  </View>
</TouchableWithoutFeedback>

解法1:禁止外层捕获事件

只要 Overlay 不捕获自组件的触摸事件就好了。View 组件有个属性叫做 pointerEvents,用来控制 View 是否可以作为触控事件的目标。(具体参考:https://facebook.github.io/react-native/docs/view.html#pointerevents)

可以通过如下写法让 Touchable* 组件内部元素的触摸事件无法被外层捕获:

<TouchableWithoutFeedback>
  <View pointerEvents="box-none">
    <ScrollView>
      { list }  
    </ScrollView>
  </View>
</TouchableWithoutFeedback>

设置 pointerEvents 的值为 box-none,让 View 无法接受触摸事件,但是它的子元素可以。这样就避免了 ScrollView 内部触摸事件被外层捕获导致卡顿。

缺点

作为 Overlay 组件的话,就无法实现“点击遮罩消失”这类功能了。

解法2:阻止 ScrollView 内部事件冒泡到外层

阻止 ScrollView 组件的事件冒泡到外层也可以解决问题。ScrollView 内部用 Touchable* 组件包裹即可:

<TouchableWithoutFeedback>
  <View>
    <ScrollView>
      <TouchableWithoutFeedback>
      { list }
      </TouchableWithoutFeedback>
    </ScrollView>
  </View>
</TouchableWithoutFeedback>

缺点

需要业务特别处理

解法3:改变层级关系

从根本解决问题,将触摸事件处理层(遮罩层)和子组件层的层级关系由父子关系改为兄弟关系,而视觉上遮罩层在子组件层后面的效果则由样式来实现:

<View>
  <TouchableWithoutFeedback>
    <View />
  </TouchableWithoutFeedback>
  { this.props.children }
</View>

缺点

无。本该如此。

总结

归根结底,还是一个设计问题。