27

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

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

4

11 回答 11

49

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" />
于 2017-11-10T18:59:46.747 回答
12

使用属性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);

于 2017-11-10T16:21:46.677 回答
6

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

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。

于 2018-04-27T04:33:08.020 回答
6

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

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

于 2018-06-19T15:21:36.517 回答
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"
          />
    );
  }
于 2019-11-18T13:10:25.850 回答
4

这是我的简单钩子。

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>
于 2021-08-06T13:48:21.227 回答
3

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

使用TimeBlockedCallback.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>
  )
}

默认情况下,回调在被调用后被阻止 1000 毫秒,但您可以使用钩子的第二个参数更改它。

于 2020-04-27T14:32:37.717 回答
1

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

   _GoCategoria(_categoria,_tipo){

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

    };
于 2018-08-19T19:58:25.477 回答
0

您还可以在等待一些异步操作时显示加载 gif。只要确保标记你的onPressasync () => {}这样它就可以了await

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;
于 2018-04-04T20:10:58.867 回答
0

我的包装器组件的实现。

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>
    )
}
于 2019-10-29T10:41:11.793 回答
0

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

于 2021-09-24T00:27:34.970 回答