React Native 防止双击

Posted

技术标签:

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

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

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

【问题讨论】:

@efru 这些答案对您的情况有用吗? 【参考方案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 文章并自己尝试了大多数解决方案之后,我得出了以下结论:


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

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

    使用 _.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')。我该如何解决这个问题?

以上是关于React Native 防止双击的主要内容,如果未能解决你的问题,请参考以下文章

React-native 组件缓存(或防止卸载)(react-navigation)

如何防止 map() 在 react-native 中一次渲染整个数组

React Native打包js时如何防止节点内存不足

React Native 如何防止键盘在文本提交时关闭?

在 Modal 中防止键盘关闭 React Native

React native - eslintrc - prettierrc:如何防止移除 JSX 中一个元素周围的括号