【问题标题】:Prevent Double tap in React native防止在 React Native 中双击
【发布时间】:2018-04-16 14:37:12
【问题描述】:

如何防止用户在 React Native 中点击两次按钮?

即用户不能在可触摸的高亮上快速点击两次

【问题讨论】:

标签: javascript react-native touchablehighlight


【解决方案1】:

https://snack.expo.io/@patwoz/withpreventdoubleclick

使用这个 HOC 来扩展 TouchableHighlight、Button 等可触摸组件...

import debounce from 'lodash.debounce'; // 4.0.8

const withPreventDoubleClick = (WrappedComponent) => {

  class PreventDoubleClick extends React.PureComponent {

    debouncedOnPress = () => {
      this.props.onPress && this.props.onPress();
    }

    onPress = debounce(this.debouncedOnPress, 300, { leading: true, trailing: false });

    render() {
      return <WrappedComponent {...this.props} onPress={this.onPress} />;
    }
  }

  PreventDoubleClick.displayName = `withPreventDoubleClick(${WrappedComponent.displayName ||WrappedComponent.name})`
  return PreventDoubleClick;
}

用法

import { Button } from 'react-native';
import withPreventDoubleClick from './withPreventDoubleClick';

const ButtonEx = withPreventDoubleClick(Button);

<ButtonEx onPress={this.onButtonClick} title="Click here" />

【讨论】:

  • 我收到一个错误unable to load lodash.debounce
  • @PrateekSurana 试试import { debounce } from 'lodash'
  • 你先生,是救生员。
  • 我没有使用 HOC 进行包装,而是尝试使用去抖动功能 onPress={debounce(this.props.action, 300, {leading: true, trailing: false)} 配置 onPress 道具,但没有成功。我仍然可以非常快速地双击,并且我的动作函数被调用了两次。
  • debounce(200, this.addNames(names))} > 没有工作,我收到了一个预期的错误函数跨度>
【解决方案2】:

使用属性Button.disabled

import React, { Component } from 'react';
import { AppRegistry, StyleSheet, View, Button } from 'react-native';

export default class App extends Component {
  
  state={
    disabled:false,
  }
  
  pressButton() {
    this.setState({
      disabled: true,
    });
    
    // enable after 5 second
    setTimeout(()=>{
       this.setState({
        disabled: false,
      });
    }, 5000)
  }
  
  render() {
    return (
        <Button
            onPress={() => this.pressButton()}
            title="Learn More"
            color="#841584"
            disabled={this.state.disabled}
            accessibilityLabel="Learn more about this purple button"
          />
    );
  }
}



// skip this line if using Create React Native App
AppRegistry.registerComponent('AwesomeProject', () => App);

【讨论】:

  • 这个实现不处理 disabled 属性的默认值。从父组件传递。您应该编辑初始状态。
  • 另外,这会导致无用的重新渲染,当您只需要不允许用户单击两次时,您会更改 UI 上按钮的状态
【解决方案3】:

我通过参考上面的答案来使用它。 “禁用”不一定是状态。

import React, { Component } from 'react';
import { TouchableHighlight } from 'react-native';

class PreventDoubleTap extends Component {
    disabled = false;
    onPress = (...args) => {
        if(this.disabled) return;
        this.disabled = true;
        setTimeout(()=>{
            this.disabled = false;
        }, 500);
        this.props.onPress && this.props.onPress(...args);
    }
}

export class ButtonHighLight extends PreventDoubleTap {
    render() {
        return (
            <TouchableHighlight
                {...this.props}
                onPress={this.onPress}
                underlayColor="#f7f7f7"
            />
        );
    }
}

可以是 TouchableOpacity 等其他可触摸组件。

【讨论】:

  • 只是提醒一下,这在我测试过的少数安卓设备上不起作用,但在某些设备上可以。
【解决方案4】:

如果您使用反应导航,则使用此格式导航到另一个页面。 this.props.navigation.navigate({key:"any",routeName:"YourRoute",params:{param1:value,param2:value}})

StackNavigator 将防止具有相同键的路由再次被压入堆栈。 您可以编写任何独特的内容,因为 keyparams 属性是可选的,如果您想将参数传递到另一个屏幕。

【讨论】:

    【解决方案5】:

    同意Accepted answer但非常简单的方法,我们可以使用以下方式

    import debounce from 'lodash/debounce';
    
        componentDidMount() {
    
           this.onPressMethod= debounce(this.onPressMethod.bind(this), 500);
      }
    
    onPressMethod=()=> {
        //what you actually want on button press
    }
    
     render() {
        return (
            <Button
                onPress={() => this.onPressMethod()}
                title="Your Button Name"
              />
        );
      }
    

    【讨论】:

    • 啊,这太干净优雅了,一条线修复!非常感谢。
    【解决方案6】:

    公认的解决方案效果很好,但它强制包装整个组件并导入 lodash 以实现所需的行为。 我写了一个自定义的 React 钩子,它可以只包装你的回调:

    useTimeBlockedCallback.js

    import { useRef } from 'react'
    
    export default (callback, timeBlocked = 1000) => {
      const isBlockedRef = useRef(false)
      const unblockTimeout = useRef(false)
    
      return (...callbackArgs) => {
        if (!isBlockedRef.current) {
          callback(...callbackArgs)
        }
        clearTimeout(unblockTimeout.current)
        unblockTimeout.current = setTimeout(() => isBlockedRef.current = false, timeBlocked)
        isBlockedRef.current = true
      }
    }
    

    用法:

    你的组件.js

    import React from 'react'
    import { View, Text } from 'react-native'
    import useTimeBlockedCallback from '../hooks/useTimeBlockedCallback'
    
    export default () => {
      const callbackWithNoArgs = useTimeBlockedCallback(() => {
        console.log('Do stuff here, like opening a new scene for instance.')
      })
      const callbackWithArgs = useTimeBlockedCallback((text) => {
        console.log(text + ' will be logged once every 1000ms tops')
      })
    
      return (
        <View>
          <Text onPress={callbackWithNoArgs}>Touch me without double tap</Text>
          <Text onPress={() => callbackWithArgs('Hello world')}>Log hello world</Text>
        </View>
      )
    }
    

    回调默认被调用后会阻塞 1000ms,但是你可以通过 hook 的第二个参数来改变它。

    【讨论】:

    • 你可能想把所有的东西都放在 if 里面的 return 函数中,否则每次按下按钮时它都会重置计时器而不再次调用该函数。
    【解决方案7】:

    这是我的简单钩子。

    import { useRef } from 'react';
    
    const BOUNCE_RATE = 2000;
    
    export const useDebounce = () => {
      const busy = useRef(false);
    
      const debounce = async (callback: Function) => {
        setTimeout(() => {
          busy.current = false;
        }, BOUNCE_RATE);
    
        if (!busy.current) {
          busy.current = true;
          callback();
        }
      };
    
      return { debounce };
    };

    这可以在您喜欢的任何地方使用。即使不是按钮。

    const { debounce } = useDebounce();
    
    <Button onPress={() => debounce(onPressReload)}>
      Tap Me again and adain!
    </Button>
    

    【讨论】:

      【解决方案8】:

      我有一个使用 runAfterInteractions 的非常简单的解决方案:

         _GoCategoria(_categoria,_tipo){
      
                  if (loading === false){
                      loading = true;
                      this.props.navigation.navigate("Categoria", {categoria: _categoria, tipo: _tipo});
                  }
                   InteractionManager.runAfterInteractions(() => {
                      loading = false;
                   });
      
          };
      

      【讨论】:

        【解决方案9】:

        您还可以在等待一些异步操作时显示加载 gif。只需确保用async () =&gt; {} 标记您的onPress,这样它就可以是await'd。

        import React from 'react';
        import {View, Button, ActivityIndicator} from 'react-native';
        
        class Btn extends React.Component {
            constructor(props) {
                super(props);
        
                this.state = {
                    isLoading: false
                }
            }
        
            async setIsLoading(isLoading) {
                const p = new Promise((resolve) => {
                    this.setState({isLoading}, resolve);
                });
                return p;
            }
        
            render() {
                const {onPress, ...p} = this.props;
        
                if (this.state.isLoading) {
                    return <View style={{marginTop: 2, marginBottom: 2}}>
                        <ActivityIndicator
                            size="large"
                        />
                    </View>;
                }
        
        
                return <Button
                    {...p}
                    onPress={async () => {
                        await this.setIsLoading(true);
                        await onPress();
                        await this.setIsLoading(false);
                    }}
                />
            }
        
        }
        
        export default Btn;
        

        【讨论】:

          【解决方案10】:

          我的包装组件实现。

          import React, { useState, useEffect } from 'react';
          import { TouchableHighlight } from 'react-native';
          
          export default ButtonOneTap = ({ onPress, disabled, children, ...props }) => {
              const [isDisabled, toggleDisable] = useState(disabled);
              const [timerId, setTimerId] = useState(null);
          
              useEffect(() => {
                  toggleDisable(disabled);
              },[disabled]);
          
              useEffect(() => {
                  return () => {
                      toggleDisable(disabled);
                      clearTimeout(timerId);
                  }
              })
          
          
              const handleOnPress = () => {
                  toggleDisable(true);
                  onPress();
                  setTimerId(setTimeout(() => {
                      toggleDisable(false)
                  }, 1000))
              }
              return (
                  <TouchableHighlight onPress={handleOnPress} {...props} disabled={isDisabled} >
                      {children}
                  </TouchableHighlight>
              )
          }
          

          【讨论】:

            【解决方案11】:

            没有使用禁用功能、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 毫秒的差异,并避免任何寄生虫双击。

            【讨论】:

              猜你喜欢
              • 2016-07-11
              • 1970-01-01
              • 1970-01-01
              • 2019-02-05
              • 2011-09-11
              • 2014-03-26
              • 2018-12-25
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多