【问题标题】:React Native Prevent Double TapReact Native 防止双击
【发布时间】:2016-07-11 06:28:41
【问题描述】:

我有一个 TouchableHighlight 包裹一个 Text 块,当点击它时,会打开一个新场景(我正在使用 react-native-router-flux)。
一切正常,除了如果你快速点击TouchableHighlight,场景可以渲染两次。
我想阻止用户快速点击该按钮。

在 Native 中实现此目的的最佳方法是什么?我查看了手势响应系统,但没有任何示例或类似的东西,如果您像我一样是新手,会感到困惑。

【问题讨论】:

  • @efru 这些答案对您的情况有用吗?

标签: react-native


【解决方案1】:

没有使用禁用功能、setTimeout 或安装额外的东西。

这种方式代码可以毫无延迟地执行。我没有避免双击,但我保证代码只运行一次。

我使用了文档https://reactnative.dev/docs/pressevent 中描述的 TouchableOpacity 返回的对象和一个状态变量来管理时间戳。 lastTime 是一个状态变量,初始化为 0。

const [lastTime, setLastTime] = useState(0);

...

<TouchableOpacity onPress={async (obj) =>{
    try{
        console.log('Last time: ', obj.nativeEvent.timestamp);
        if ((obj.nativeEvent.timestamp-lastTime)>1500){  
            console.log('First time: ',obj.nativeEvent.timestamp);
            setLastTime(obj.nativeEvent.timestamp);

            //your code
            SplashScreen.show();
            await dispatch(getDetails(item.device));
            await dispatch(getTravels(item.device));
            navigation.navigate("Tab");
            //end of code
        }
        else{
            return;
        }
    }catch(e){
        console.log(e);
    }           
}}>

我正在使用异步函数来处理实际获取数据的调度,最后我基本上是导航到其他屏幕。

我在触摸之间的第一次和最后一次打印。我选择它们之间至少存在 1500 毫秒的差异,并避免任何寄生虫双击。

【讨论】:

    【解决方案2】:

    这对我来说是一种解决方法

    import React from 'react';
    import {TouchableOpacity } from 'react-native';
    
    export default SingleClickTouchableOpacity = (props) => {
    let buttonClicked = false
    return(
        <TouchableOpacity {...props}  onPress={() => {
            if(buttonClicked){
                return
            }
            props.onPress();
            buttonClicked = true 
            setTimeout(() => {
                buttonClicked = false
              }, 1000);
        }}>
            {props.children}
        </TouchableOpacity>
    )
    }
    

    【讨论】:

      【解决方案3】:

      我在下面使用这个 lodash 方法进行了修复,

      第一步

      import { debounce } from 'lodash';
      

      第 2 步

      将此代码放入构造函数中

      this.loginClick = debounce(this.loginClick .bind(this), 1000, {
                  leading: true,
                  trailing: false,
      });
      

      第三步

      像这样写在你的 onPress 按钮上

      onPress={() => this.props.skipDebounce ? this.props.loginClick : this.loginClick ()}
      

      谢谢,

      【讨论】:

        【解决方案4】:

        你可以使用 debounce 非常简单的方法

        import debounce from 'lodash/debounce';
        
        componentDidMount() {
        
               this.yourMethod= debounce(this.yourMethod.bind(this), 500);
          }
        
         yourMethod=()=> {
            //what you actually want your TouchableHighlight to do
        }
        
        <TouchableHighlight  onPress={this.yourMethod}>
         ...
        </TouchableHighlight >
        

        【讨论】:

          【解决方案5】:

          你想要做的是限制你的点击回调,这样它们只会运行一次。

          这称为节流,您可以为此使用underscore方法如下:

          _.throttle(
              this.thisWillRunOnce.bind(this),
              200, // no new clicks within 200ms time window
          );
          

          这就是我的 react 组件的外观。

          class MyComponent extends React.Component {
            constructor(props) {
              super(props);
               _.throttle(
                  this.onPressThrottledCb.bind(this),
                  200, // no new clicks within 200ms time window
              );
            }
            onPressThrottledCb() {
              if (this.props.onPress) {
                this.props.onPress(); // this only runs once per 200 milliseconds
              }
            }
            render() {
              return (
                <View>
                  <TouchableOpacity onPress={this.onPressThrottledCb}>
                  </TouchableOpacity>
                </View>
              )
            }
          }
          

          我希望这对你有帮助。 如果您想了解更多信息,请查看this thread

          【讨论】:

          • 通过这种方式,我无法将参数传递给这样的函数onPress={()=&gt;this.onPressThrottledCb(data)}
          • lodash 或 underscore 的油门实现自动将所有参数传递给函数
          • 现在使用下划线,并且参数确实通过了。
          • @Maximtoyberman 你是什么意思它不起作用?你有任何错误吗?你能调试看看你的回调是否被调用了吗?
          • 如果方法被定义为类属性,是否仍然需要使用this.onPressThrottledCb.bind(this) 而不是this.onPressThrottledCb?即onPressThrottledCb = () =&gt; {...}
          【解决方案6】:

          我是这样做的:

          link(link) {
                  if(!this.state.disabled) {
                      this.setState({disabled: true});
                      // go link operation
                      this.setState({disabled: false});
                  }
              }
              render() {
                  return (
                      <TouchableHighlight onPress={() => this.link('linkName')}>
                        <Text>Go link</Text>
                      </TouchableHighlight>
                  );
              }
          

          【讨论】:

            【解决方案7】:

            在阅读了几个 github 线程、SO 文章并自己尝试了大多数解决方案之后,我得出了以下结论:


            1. 到目前为止,提供额外的 key 参数来执行“幂等推送”并不能始终如一地工作。 https://github.com/react-navigation/rfcs/issues/16

            2. 使用debounce 会显着降低用户体验。导航仅在用户最后一次按下按钮后发生 X 毫秒。 X 需要足够大以跨越可能发生双击的时间。这实际上可能是 100-600 毫秒。

            3. 使用 _.throttle 对我不起作用。它保存了受限制的函数调用,并在计时器用完后执行它,从而导致双击延迟。


            我考虑过转移到react-native-navigation,但显然问题更深层次,他们也经历过。

            所以现在我构建了自己的 hack,对我的代码的干扰最小:

            const preventDoubleTapHack = (component: any, doFunc: Function) => {
              if (!component.wasClickedYet__ULJyRdAvrHZvRrT7) {
                //  eslint-disable-next-line no-param-reassign
                component.wasClickedYet__ULJyRdAvrHZvRrT7 = true;
                setTimeout(() => {
                  //  eslint-disable-next-line no-param-reassign
                  component.wasClickedYet__ULJyRdAvrHZvRrT7 = false;
                }, 700);
                doFunc();
              }
            };
            

            任何地方,我们在哪里导航而不是

            this.props.navigation.navigate('MyRoute');
            

            preventDoubleTapHack(this, () => this.props.navigation.navigate('MyRoute');
            

            美丽。

            【讨论】:

              【解决方案8】:

              如果您使用react-navigation,您可以向navigate 提供key 属性,以确保只有一个实例被添加到堆栈中。

              通过https://github.com/react-navigation/react-navigation/issues/271

              【讨论】:

                【解决方案9】:

                您可以在实际接收器方法中反弹点击,尤其是在您处理视觉效果的状态时。

                _onRefresh() {    
                    if (this.state.refreshing)
                      return
                    this.setState({refreshing: true});
                

                【讨论】:

                  【解决方案10】:

                  我通过创建一个在传递的时间间隔内只调用一次函数的模块来修复这个错误。

                  示例:如果您希望从 Home -> About 导航,然后在 400 毫秒内按两次 About 按钮。

                  navigateToAbout = () => dispatch(NavigationActions.navigate({routeName: 'About'}))
                  
                  const pressHandler = callOnce(navigateToAbout,400);
                  <TouchableOpacity onPress={pressHandler}>
                   ...
                  </TouchableOpacity>
                  The module will take care that it calls navigateToAbout only once in 400 ms.
                  

                  这里是 NPM 模块的链接:https://www.npmjs.com/package/call-once-in-interval

                  【讨论】:

                    【解决方案11】:

                    也许您可以使用 0.22 中为可触摸元素引入的新禁用功能?我在想这样的事情:

                    组件

                    <TouchableHighlight ref = {component => this._touchable = component}
                                        onPress={() => this.yourMethod()}/>
                    

                    方法

                    yourMethod() {
                        var touchable = this._touchable;
                        touchable.disabled = {true};
                    
                        //what you actually want your TouchableHighlight to do
                    }
                    

                    我自己没试过。所以我不确定它是否有效。

                    【讨论】:

                    • 如果用户从硬编码的 android 后退按钮返回,如何启用它。?
                    【解决方案12】:

                    通过防止两次路由到同一路由来起作用:

                    import { StackNavigator, NavigationActions } from 'react-navigation';
                    
                    const App = StackNavigator({
                        Home: { screen: HomeScreen },
                        Details: { screen: DetailsScreen },
                    });
                    
                    // Prevents double taps navigating twice
                    const navigateOnce = (getStateForAction) => (action, state) => {
                        const { type, routeName } = action;
                        return (
                            state &&
                            type === NavigationActions.NAVIGATE &&
                            routeName === state.routes[state.routes.length - 1].routeName
                        ) ? state : getStateForAction(action, state);
                    };
                    App.router.getStateForAction = navigateOnce(App.router.getStateForAction);
                    

                    【讨论】:

                    • 如果你使用同一个屏幕两次,这将失败。例如一个测验应用程序。
                    • 是的,因为答案表明它通过防止两次去同一条路线来工作。如果这不合适,请使用其他方法。在大多数情况下它是合适的
                    • 在我之前的项目中它工作正常,但现在它会产生一个警告:undefined is not an object (evalating 'routeConfigs[initialRouteName].params')。我该如何解决这个问题?
                    猜你喜欢
                    • 2018-04-16
                    • 1970-01-01
                    • 2019-02-05
                    • 2011-09-11
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多